我正在尝试在我的电子邮件备份中搜索一封重要的电子邮件。它是一个带有子目录的目录,其中包含数千个.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}
在每个新文件的开头,这会将变量
a
、b
和设置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}
在每个文件的末尾,if
a
和b
and不是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
仅返回状态码- 如果您想搜索模式而不是单词,请省略
-F
fromgrep
- 添加
-w
到grep
仅匹配整个单词:匹配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
报告未找到匹配项的文件,因此我们最终得到包含foo
和bar
的文件不是 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
希望这会有所帮助:)