查找和 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 都会成功(因为 passednot 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 greppcregrep:

find . -wholename '*_done/(*Report*.docx' -not -wholename '*appendix*' \
-exec sh -c '[ $(docx2txt "[=11=]" - | grep -Pom2 "(?<!not )passed" | wc -l) = 2 ]' {} \; -print