如何在文件中搜索多个字符串(即全文搜索而非基于行的搜索)?使用grep命令。

98

我想要用grep命令查找包含单词 Dansk, SvenskaNorsk 的文件,且要有可用的返回码(因为我只希望得到这些字符串是否存在的信息,我的一行命令还需要更多操作)。

我有很多包含这样一行文本的文件:

Disc Title: unknown
Title: 01, Length: 01:33:37.000 Chapters: 33, Cells: 31, Audio streams: 04, Subpictures: 20
        Subtitle: 01, Language: ar - Arabic, Content: Undefined, Stream id: 0x20, 
        Subtitle: 02, Language: bg - Bulgarian, Content: Undefined, Stream id: 0x21, 
        Subtitle: 03, Language: cs - Czech, Content: Undefined, Stream id: 0x22, 
        Subtitle: 04, Language: da - Dansk, Content: Undefined, Stream id: 0x23, 
        Subtitle: 05, Language: de - Deutsch, Content: Undefined, Stream id: 0x24, 
(...)

这是我想要的伪代码:

for all files in directory;
 if file contains "Dansk" AND "Norsk" AND "Svenska" then
 then echo the filename
end

哪种方法最好?能不能一行代码实现?

17个回答

98

您可以使用:

grep -l Dansk * | xargs grep -l Norsk | xargs grep -l Svenska

如果你也想查找隐藏文件:

grep -l Dansk .* | xargs grep -l Norsk | xargs grep -l Svenska

1
聪明的解决方案;需要注意的一件事(一般而言,与OP所询问的内容无关)是即使在(概念上的)失败情况下,总体的_exit code_也将为_0_。因此,如果您想确定失败与成功,您要么必须检查stdout输出是否为空,要么使用@EddSteel的方法。 - mklement0
在 Bash 中,PIPESTATUS 数组包含管道成员的退出值。 - Dennis Williamson
@DennisWilliamson 很好知道,谢谢。另一个选项是打开 pipefail shell 选项(临时):shopt -so pipefail - mklement0
4
如果您的文件名包含空格,建议使用grep -Zxargs -0 - Ben Challenor
1
如果你有很多文件,这可能会导致“参数列表过长”的错误。 - AnnanFay

24
grep –irl word1 * | grep –il word2 `cat -` | grep –il word3 `cat -`
  • -i 使搜索忽略大小写
  • -r 使文件搜索递归地进行,穿过所有的子目录
  • -l 在输出中列出包含该词的文件名
  • cat - 导致下一个 grep 命令搜索传递给它的文件列表。

2
这是最简单和最直接的答案,非常有帮助,谢谢! - majick
有趣的grep/xargs版本变化 - andrej
有趣的grep/xargs版本变化 - undefined

24

使用 bash 和 grep 的另一种方法:

对于单个文件 'test.txt':

  grep -q Dansk test.txt && grep -q Norsk test.txt && grep -l Svenska test.txt

如果文件包含这三个内容中的任何组合,则会打印test.txt。前两个grep不会打印任何东西(使用-q选项),最后一个只有在前两个命令通过时才会打印该文件。

如果你想对目录中的每个文件执行相同的操作:

  for f in *; do grep -q Dansk $f && grep -q Norsk $f && grep -l Svenska $f; done

但是这样就没有必要执行3次grep了。 - kurumi
1
我知道你可以使用-e将模式组合起来,但我无法看到在grep中单独制作一个连接词的方法。 - Edd Steel
1
很好;关于 for f ...:使用双引号 "$f" 而不是 $f,以确保正确处理包含空格等嵌入式文件名。 - mklement0
这种方法相对于@vmpstr的方法的优势在于,退出码可以正确反映是否找到了所有搜索词。 - mklement0

10

你可以使用ack轻松实现这一点:

ack -l 'cats' | ack -xl 'dogs'
  • -l: 返回文件列表
  • -x: 从标准输入(前一次搜索)获取文件并仅搜索这些文件

您可以继续使用管道,直到获得所需的文件。


当我尝试这个时,它会显示“未知选项:x”。是否有某个版本的ack支持此x标志? - user377628

8
如何在文件中搜索多个不同行的字符串(使用管道符号):

for file in *;do 
   test $(grep -E 'Dansk|Norsk|Svenska' $file | wc -l) -ge 3 && echo $file
done

注意事项:

  1. 如果您在grep中使用双引号"",则必须像这样转义管道符\|以搜索Dansk、Norsk和Svenska。

  2. 假设一行只有一种语言。

步骤说明:http://www.cyberciti.biz/faq/howto-use-grep-command-in-linux-unix/


如果丹麦语、挪威语和瑞典语都出现在同一行,那么这不会失败吗? - vmpstr
是的,在那种情况下会失败。我假设语言是每行一个出现的。 - Damodharan R
如果我只有“Norsk”,但是分别在三行上,也可以进行文件。 - Benjamin W.

5
awk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print "0" }' 

你可以使用shell来捕获返回值。

如果你有Ruby(1.9+)

ruby -0777 -ne 'print if /Dansk/ and /Norsk/ and /Svenka/' file

1
在你的 awk 的 END 子句中,你可能想要使用:if (a && b && c) {exit 0} else {exit 1},或者更简洁地写为 exit !(a && b && c) - glenn jackman
你的 Ruby 解决方案似乎不正确。它只会打印包含所有搜索词的段落。问题是:文件(作为一个整体)是否包含所有单词,即使它们不都出现在同一段落中。 - glenn jackman
谢谢。如果需要整个文件,则必须使用-0777进行更改。 - kurumi

4

这可以在多个文件中搜索多个单词:

egrep 'abc|xyz' file1 file2 ..filen 

4
除了找到同时包含这两个字符串的文件,这也会找到只有'abc'或'xyz'的文件。我认为 OP 是在寻找同时包含'abc'和'xyz'的文件。 - Chris Warth

2
这是 glenn jackman 和 kurumi 的答案的结合,允许使用任意数量的正则表达式,而不是固定数量的单词或一组固定的正则表达式。
#!/usr/bin/awk -f
# by Dennis Williamson - 2011-01-25

BEGIN {
    for (i=ARGC-2; i>=1; i--) {
        patterns[ARGV[i]] = 0;
        delete ARGV[i];
    }
}

{
    for (p in patterns)
        if ($0 ~ p)
            matches[p] = 1
            # print    # the matching line could be printed
}

END {
    for (p in patterns) {
        if (matches[p] != 1)
            exit 1
    }
}

像这样运行:

./multigrep.awk Dansk Norsk Svenska 'Language: .. - A.*c' dvdfile.dat

2
以下是我成功的经验:

这对我来说非常有效:

find . -path '*/.svn' -prune -o -type f -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh
./another/path/to/file2.txt
./blah/foo.php

如果我只想查找.sh文件,那么可以使用以下三个命令之一:
find . -path '*/.svn' -prune -o -type f -name "*.sh" -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh

2

简而言之:

grep 'word1\|word2\|word3' *

请查看这篇文章了解更多信息。

除了加上“-l”标志外,我认为这个答案最直接了当,除非我漏掉了什么。 - xdhmoore
是的,这也更有效率,因为您不需要在多个管道和过滤器中处理所有数据。 - moshe beeri
5
该问题询问一个表达式,返回包含所有三个术语的文件;但是该表达式返回包含任意三个术语(而不是所有三个)的行(而不是文件名)。 - Benjamin W.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接