试图递归地找到出现在一个文件中的三个单词

dry*_*dwy 2 grep

我正在尝试在我的电子邮件备份中搜索一封重要的电子邮件。它是一个带有子目录的目录,其中包含几千个.eml文件(在 Linux 文件系统上)。我想搜索.eml包含三个单词的文本文件并排除一个单词。

首先,我尝试搜索一个词,然后用管道搜索另一个词。

grep -R 'foo' ~/Directory/path | grep 'bar'
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为它只返回包含同一行上的两个单词的文件。我需要在整个文件中包含两个单词的文件。

我尝试查找包含一个单词的文件并将文件内容通过管道传输到输出文件。

grep -rIlZ  '.' -e 'foo' | xargs -0 cat > MyOutputFile 
Run Code Online (Sandbox Code Playgroud)

这很有帮助,因为我可以看到上下文。但我需要搜索不止一个词。是否可以将其扩展为搜索多个单词并排除一个单词?

Joh*_*024 5

假设我们想要包含foobar 包含baz. 在这种情况下:

find . -type f -exec gawk '
  BEGINFILE{a=b=c=0}
  /foo/{a=1} /bar/{b=1} /baz/{c=1;nextfile}
  ENDFILE{if(a && b && !c)print FILENAME}' {} +
Run Code Online (Sandbox Code Playgroud)

[由于您使用的是 Linux,我假设您已准备好访问 GNU awk (gawk)。]

请注意,在这种方法中,会启动尽可能少的 awk 调用,并且每个文件只读取一次。不需要中间文件。这应该是有效的。

例子

让我们考虑一个包含两个文件的目录:

$ cat file1.eml 
foo and
bar only
$ cat file2.eml 
foo
and
bar
and
baz
Run Code Online (Sandbox Code Playgroud)

如果我们运行我们的命令,它会生成./file1.eml唯一满足要求的文件:

$ find . -type f -exec gawk '
    BEGINFILE{a=b=c=0}
    /foo/{a=1} /bar/{b=1} /baz/{c=1;nextfile}
    ENDFILE{if(a && b && !c)print FILENAME}' {} +
./file1.eml
Run Code Online (Sandbox Code Playgroud)

这个怎么运作

  • find递归收集常规文件列表并传递它gawk

  • BEGINFILE{a=b=c=0}

    在每个新文件的开始,这将变量ab以及c零(假)。

  • /foo/{a=1}

    如果任何行包含foo,则将变量设置a为 1。(真的)。

  • /bar/{b=1}

    如果任何行包含bar,则将变量设置b为 1。(真的)。

  • /baz/{c=1;nextfile}

    如果任何行包含baz,则将变量设置c为 1。(真的)。

    在找到任何要排除的单词之后,例如baz在我们的示例中,再读取文件就没有意义了。因此,我们运行nextfile以跳过其余的行并立即转到 ENDFILE。

  • ENDFILE{if(a && b && !c)print FILENAME}

    在每个文件的末尾,如果ab c(在awk 中!是逻辑非)都为真,则打印文件名。

非 GNU awk

如果你的awk没有漂亮BEGINFILEENDFILE功能,比如mawk,你需要运行一个awk每个文件:

find . -type f -exec mawk '
  /foo/{a=1} /bar/{b=1} /baz/{c=1;exit}
  END{if(a && b && !c) print FILENAME}' {} \;
Run Code Online (Sandbox Code Playgroud)

或(帽子提示:Ed Morton):

awk 'FNR==1 { if (a && b && !c) print fname; fname=FILENAME; a=b=c=0 } /foo/{a=1} /bar/{b=1} /baz/{c=1}   END{if(a && b && !c) print FILENAME}' *.eml
Run Code Online (Sandbox Code Playgroud)

或者,使用递归搜索:

find . -type f -exec awk 'FNR==1 { if (a && b && !c) print fname; fname=FILENAME; a=b=c=0 } /foo/{a=1} /bar/{b=1} /baz/{c=1}   END{if(a && b && !c) print FILENAME}' {} +
Run Code Online (Sandbox Code Playgroud)