查找和 grep:获取文件名
find and grep: get filenames
我需要找到报告(.docx 文件),使用 docx2txt
阅读它们,找到“通过”的第二个匹配项(不包括“未通过”)并将这些文件名保存到文本文件中。这是我尝试过的:
OIFS="$IFS"
IFS=$'\n'
for f in $(find . -wholename '*_done/(*Report*.docx' |grep -v appendix)
do
docx2txt "$f" - | (grep -q -m2 passed || grep -q -v "not passed") || echo $f >> failed
done
IFS="$OIFS"
但是这个脚本给了我一个空文件。如果我在 echo
之前将 ||
替换为 &&
,所有文件名都会存储到文件中。如果 grep
不在脚本中,docx2txt
也能正常工作。我在这里做错了什么?
当您 运行 遇到这样的问题时,最好删除尽可能多的代码。如果我们只使用包含多个 grep
语句的那一行,我们可以首先验证当前表达式是否不起作用:
$ echo passed | ((grep -q -m2 passed || grep -q -v "not passed") || echo failed
$ echo not passed | ((grep -q -m2 passed || grep -q -v "not passed") || echo failed
我们可以看到这两个命令都没有产生任何输出。
仔细想想其中的逻辑:
||
运算符的意思是“如果第一个命令不成功,运行 第二个命令”。所以在这两种情况下,第一个 grep 都会成功(因为 passed
和 not passed
都包含短语 passed
)。这意味着第二个 grep 永远不会 运行、 和 这意味着由于第一个命令成功,整个 grep ... || grep ...
命令将成功,这意味着最终 echo $f
永远不会 运行.
我一直在想一个聪明的方法来解决这个问题,但如果我们使用一个临时文件似乎最简单:
OIFS="$IFS"
IFS=$'\n'
tmpfile=$(mktemp docXXXXXX)
trap "rm -f $tmpfile" EXIT
for f in $(find . -wholename '*_done/(*Report*.docx' |grep -v appendix)
do
docx2txt "$f" - | head -2 > $tmpfile
if grep -q passed $tmpfile && ! grep -q 'not passed' $tmpfile; then
echo $f >> failed
fi
done
IFS="$OIFS"
grep 命令有很多问题。
grep -q
总是 在第一场比赛中成功退出。
对于-q
,-m2
没有效果。如果有一个匹配 grep
成功退出。它不检查是否有第二个匹配项。
要检查是否有(至少)两个匹配项,请对匹配项进行计数,然后使用 test
/[ ]
检查找到的匹配项的数量。如果每行最多有一个 passed
,那么 grep -c
就足够了。如果每行可以有多个匹配项,则需要 grep -o ... | wc -l
.
-q
和-v
一起表示:是否至少有一行不包含该模式?当grep
找到这样的一行就成功退出了。此命令失败的唯一方法是每行包含 not passed
(这包括空文件)的输入。
匹配 passed
但不匹配 not passed
比人们想象的要棘手。如果每行最多可以有一个passed
/not passed
,则可以使用grep -v 'not passed' | grep passed
。否则你需要一个negative lookbehind,它只在perl兼容的正则表达式(PCRE)中可用。
除此之外 command | (grep ... || grep ...)
可能无法达到您的预期。 command
只产生一次输出。在第一个 grep
读取部分输出后,读取的部分消失了。第二个 grep
将继续阅读第一个 grep
停止的地方。
顺便说一句:使用 -not
和 -exec
.
可以将 for … in $(find … | grep -v …)
变成一个安全的 find
命令
解决方案
如果每行最多包含一个passed
/not passed
,则使用
find . -wholename '*_done/(*Report*.docx' -not -wholename '*appendix*' \
-exec sh -c '[ $(docx2txt "[=10=]" - | grep -v "not passed" | grep -cm2 passed) = 2 ]' {} \; -print
如果每行可以有多个passed
/not passed
,你需要GNU grep
或pcregrep
:
find . -wholename '*_done/(*Report*.docx' -not -wholename '*appendix*' \
-exec sh -c '[ $(docx2txt "[=11=]" - | grep -Pom2 "(?<!not )passed" | wc -l) = 2 ]' {} \; -print
我需要找到报告(.docx 文件),使用 docx2txt
阅读它们,找到“通过”的第二个匹配项(不包括“未通过”)并将这些文件名保存到文本文件中。这是我尝试过的:
OIFS="$IFS"
IFS=$'\n'
for f in $(find . -wholename '*_done/(*Report*.docx' |grep -v appendix)
do
docx2txt "$f" - | (grep -q -m2 passed || grep -q -v "not passed") || echo $f >> failed
done
IFS="$OIFS"
但是这个脚本给了我一个空文件。如果我在 echo
之前将 ||
替换为 &&
,所有文件名都会存储到文件中。如果 grep
不在脚本中,docx2txt
也能正常工作。我在这里做错了什么?
当您 运行 遇到这样的问题时,最好删除尽可能多的代码。如果我们只使用包含多个 grep
语句的那一行,我们可以首先验证当前表达式是否不起作用:
$ echo passed | ((grep -q -m2 passed || grep -q -v "not passed") || echo failed
$ echo not passed | ((grep -q -m2 passed || grep -q -v "not passed") || echo failed
我们可以看到这两个命令都没有产生任何输出。
仔细想想其中的逻辑:
||
运算符的意思是“如果第一个命令不成功,运行 第二个命令”。所以在这两种情况下,第一个 grep 都会成功(因为 passed
和 not passed
都包含短语 passed
)。这意味着第二个 grep 永远不会 运行、 和 这意味着由于第一个命令成功,整个 grep ... || grep ...
命令将成功,这意味着最终 echo $f
永远不会 运行.
我一直在想一个聪明的方法来解决这个问题,但如果我们使用一个临时文件似乎最简单:
OIFS="$IFS"
IFS=$'\n'
tmpfile=$(mktemp docXXXXXX)
trap "rm -f $tmpfile" EXIT
for f in $(find . -wholename '*_done/(*Report*.docx' |grep -v appendix)
do
docx2txt "$f" - | head -2 > $tmpfile
if grep -q passed $tmpfile && ! grep -q 'not passed' $tmpfile; then
echo $f >> failed
fi
done
IFS="$OIFS"
grep 命令有很多问题。
grep -q
总是 在第一场比赛中成功退出。
对于
-q
,-m2
没有效果。如果有一个匹配grep
成功退出。它不检查是否有第二个匹配项。
要检查是否有(至少)两个匹配项,请对匹配项进行计数,然后使用test
/[ ]
检查找到的匹配项的数量。如果每行最多有一个passed
,那么grep -c
就足够了。如果每行可以有多个匹配项,则需要grep -o ... | wc -l
.-q
和-v
一起表示:是否至少有一行不包含该模式?当grep
找到这样的一行就成功退出了。此命令失败的唯一方法是每行包含not passed
(这包括空文件)的输入。
匹配passed
但不匹配not passed
比人们想象的要棘手。如果每行最多可以有一个passed
/not passed
,则可以使用grep -v 'not passed' | grep passed
。否则你需要一个negative lookbehind,它只在perl兼容的正则表达式(PCRE)中可用。
除此之外 command | (grep ... || grep ...)
可能无法达到您的预期。 command
只产生一次输出。在第一个 grep
读取部分输出后,读取的部分消失了。第二个 grep
将继续阅读第一个 grep
停止的地方。
顺便说一句:使用 -not
和 -exec
.
for … in $(find … | grep -v …)
变成一个安全的 find
命令
解决方案
如果每行最多包含一个passed
/not passed
,则使用
find . -wholename '*_done/(*Report*.docx' -not -wholename '*appendix*' \
-exec sh -c '[ $(docx2txt "[=10=]" - | grep -v "not passed" | grep -cm2 passed) = 2 ]' {} \; -print
如果每行可以有多个passed
/not passed
,你需要GNU grep
或pcregrep
:
find . -wholename '*_done/(*Report*.docx' -not -wholename '*appendix*' \
-exec sh -c '[ $(docx2txt "[=11=]" - | grep -Pom2 "(?<!not )passed" | wc -l) = 2 ]' {} \; -print