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

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

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

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

grep -R 'foo' ~/Directory/path | grep 'bar'

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

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

grep -rIlZ  '.' -e 'foo' | xargs -0 cat > MyOutputFile 

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

答案1

foo假设我们想要包含并且bar但是的文件名不是 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}' {} +

[由于您使用的是 Linux,我假设您已经可以访问 GNU awk (gawk)。]

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

例子

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

$ cat file1.eml 
foo and
bar only
$ cat file2.eml 
foo
and
bar
and
baz

如果我们运行命令,它会生成./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

怎么运行的

  • 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}

    在每个文件的末尾,ifaband不是 c(在 awk 中!是逻辑 - 不是)都为真,然后打印文件的名称。

非 GNU awk

如果您的 awk 没有良好的BEGINFILE功能ENDFILE,例如mawk,您需要为awk每个文件运行一个:

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

或(提示:埃德·莫顿):

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

或者,使用递归搜索:

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}' {} +

答案2

尝试find -exec使用grep -q

find /my/path -name "*.eml" \
  -exec grep -F -q "word1" {} \; \
  -exec grep -F -q "word2" {} \; \
  -exec grep -F -q "word3" {} \; \
  ! -exec grep -F -q "word4" {} \; \
  -print
  • grep -q仅返回状态码
  • 如果您想搜索模式而不是单词,请省略-Ffromgrep
  • 添加-wgrep仅匹配整个单词:匹配word但不匹配someword
  • find链接命令-exec并在其中一个失败时停止(当grep -q返回错误代码时)

答案3

您可以使用如下方法:

grep -rIlZe foo . |
  xargs -r0 grep -lZe bar |
  xargs -r0 grep -LZe baz |
  xargs -r0 cat > MyOutputFile

grep也就是说,将第一个生成的文件列表提供给xargs -r0下一个grep,从而进一步细化列表。

请注意-L最后一个选项,grep它类似于-l报告未找到匹配项的文件,因此我们最终得到包含foobar的文件不是 baz

-r仅需要 和或-I第一个grep。后者将获取常规文件列表作为参数(二进制文件已在-I第一个文件中过滤掉grep),而不是要r递归的目录。

这意味着文件的内容最终可能会被读取多次,这不是很有效,但grep实现通常比实现快得多awk,而且由于上述所有 4 个命令都是并行启动的,其中一些处理将由多个处理器同时执行,并且数据已缓存在内存中,它可能比awk基于的处理器更快。

答案4

只需将这段代码复制并粘贴到新的 bash 脚本文件中,保存并chmod +x <file>在终端中运行它即可列出包含以下内容的所有文件“富”“酒吧”并且不包含“拉布”字符串:

#!/bin/bash
function notcontain {
        for FILE in $(find . 2> /dev/null); do
                if ! grep "rab" $FILE > /dev/null 2>&1; then
                        echo $FILE
                fi
        done
}
    
for FILE in `notcontain`; do
        if grep "foo" $FILE > /dev/null 2>&1 | grep "bar" $FILE > /dev/null 2>&1; then
                echo $FILE
        fi
done

希望这会有所帮助:)

相关内容