如何使用grep跨多行查找模式?

我想按这个顺序查找具有“abc”和“efg”的文件,这两个字符串在该文件中位于不同的行上。 例如:包含以下内容的文件:

blah blah.. blah blah.. blah abc blah blah blah.. blah blah.. blah blah.. blah efg blah blah blah blah.. blah blah.. 

应该匹配。

Grep对于这个操作是不够的。

在大多数现代Linux系统中都可以使用pcregrep

 pcregrep -M 'abc.*(\n|.)*efg' test.txt 

还有一个更新的pcre2grep 。 两者都由PCRE项目提供 。

pcre2grep可用于Mac OS X通过Mac端口作为端口pcre2一部分:

 % sudo port install pcre2 

并通过Homebrew作为:

 % brew install pcre 

我不确定是否可以使用grep,但sed使它非常容易:

 sed -e '/abc/,/efg/!d' [file-with-content] 

这里有一个解决方案的启发https://stackoverflow.com/a/7167115(@MichaelMior – 感谢您的链接)。

如果'abc'和'efg'可以在同一行:

 grep -zl 'abc.*efg' <your list of files> 

如果'abc'和'efg'必须在不同的行上:

 grep -Pzl '(?s)abc.*\n.*efg' <your list of files> 

-z将输入视为一组行,每个行以零字节而不是换行符结尾。 即grep威胁输入作为一个大的线。

-l打印每个输入文件的名称,通常从哪个输出文件打印出来。

(?s)激活PCRE_DOTALL,这意味着'。' 找到任何字符或换行符。

sed应该就像上面提到的海报LJ一样,

而不是!d,您可以简单地使用p打印:

 sed -n '/abc/,/efg/p' file 

这可以通过首先使用tr用其他字符替换换行符来轻松完成:

 tr '\n' '\a' | grep 'abc.*def' | tr '\a' '\n' 

在这里,我正在使用警报字符\a (ASCII 7)代替换行符。 这几乎从来没有在你的文本中找到, grep可以匹配一个. ,或者与\a匹配。

如果你可以使用Perl,你可以非常轻松地做到这一点。

 perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt 

你也可以用一个单一的正则表达式来做到这一点,但是这需要把整个文件内容放到一个单独的字符串中,这可能最终会占用大量文件的内存。 为了完整性,这里是这个方法:

 perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt 

我非常依赖pcregrep,但是对于更新的grep,您不需要为其许多功能安装pcregrep。 只要使用grep -P

在OP的问题的例子中,我认为下面的选项很好地工作,第二个最好的匹配我如何理解这个问题:

 grep -Pzo "abc(.|\n)*efg" /tmp/tes* grep -Pzl "abc(.|\n)*efg" /tmp/tes* 

我将文本复制为/ tmp / test1并删除了'g'并保存为/ tmp / test2。 这里是输出显示第一个显示匹配的字符串,第二个显示只有文件名(典型的-o是显示匹配,典型的-l只显示文件名)。 请注意,'z'对于多行是必要的,'(。| \ n)'意思是匹配“除换行符之外的任何内容”或“换行符”

 user@host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes* /tmp/test1:abc blah blah blah.. blah blah.. blah blah.. blah efg user@host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes* /tmp/test1 

要确定你的版本是否足够新,运行man grep ,看看是否有类似的东西出现在顶部附近:

  -P, --perl-regexp Interpret PATTERN as a Perl regular expression (PCRE, see below). This is highly experimental and grep -P may warn of unimplemented features. 

这是从GNU grep 2.10。

我不知道如何用grep来做,但是我会用awk做这样的事情:

 awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo 

但是,您需要小心如何做到这一点。 你想要正则表达式匹配子字符串还是整个单词? 根据需要添加\ w标签。 另外,虽然这严格符合你如何陈述这个例子,但是在efg之后第二次出现abc的时候,它并不能工作。 如果你想处理这个问题,可以在/ abc / case中添加if。

可悲的是,你不能。 从grep文档:

grep搜索命名的输入文件(如果没有文件命名,或者如果给定一个连字符 – ( – )作为文件名)标准输入包含与给定的PATTERN匹配的

awk单行:

 awk '/abc/,/efg/' [file-with-content] 

几天前我发布了一个grep替代方案,直接支持这个方法,无论是通过多行匹配还是使用条件 – 希望对于在这里搜索的人来说是有用的。 这就是这个例子的命令的样子:

多行: sift -lm 'abc.*efg' testfile
条件: sift -l 'abc' testfile --followed-by 'efg'

您也可以指定'efg'必须在特定行数内跟随'abc':
sift -l 'abc' testfile --followed-within 5:'efg'

您可以在sift-tool.org上找到更多信息。

虽然sed选项是最简单和最简单的,但LJ的单线程可悲的是不是最便携的。 那些坚持使用C Shell的版本将需要摆脱他们的刘海:

 sed -e '/abc/,/efg/\!d' [file] 

这不幸的是不能在bash等人工作。

 #!/bin/bash shopt -s nullglob for file in * do r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file) if [ "$r" -eq 1 ];then echo "Found pattern in $file" else echo "not found" fi done 

你可以使用grep来加入你不喜欢的模式序列。

 grep -l "pattern1" filepattern*.* | xargs grep "pattern2" 

 grep -l "vector" *.cpp | xargs grep "map" 

grep -l将找到与第一个模式匹配的所有文件,而xargs将为第二个模式使用grep。 希望这可以帮助。

如果你愿意使用上下文,这可以通过键入来实现

 grep -A 500 abc test.txt | grep -B 500 efg 

这将显示“abc”和“efg” 之间的所有内容,只要它们在500线之内。

与银搜索者 :

 ag 'abc.*(\n|.)*efg' 

类似于持票人的答案,而是用ag代替。 寻银者的速度优势可能会在这里闪耀。

如果你需要两个单词相互靠近,例如不超过3行,你可以这样做:

 find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg" 

同样的例子,但只过滤* .txt文件:

 find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg" 

而且,如果你想用正则表达式找到,你也可以用egrep命令替换grep命令。

作为Balu Mohan的答案的替代,可以仅使用grepheadtail来强制执行模式的顺序:

 for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done 

不过这个不是很漂亮。 格式化更易读:

 for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \ | grep -q "pattern2" \ && echo $f done 

这将打印所有在"pattern2"之后出现"pattern1"文件的名称, 或者同时出现在同一行

 $ echo "abc def" > a.txt $ echo "def abc" > b.txt $ echo "abcdef" > c.txt; echo "defabc" > d.txt $ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done a.txt c.txt d.txt 

说明

  • tail -n +i – 打印第i行之后的所有行
  • grep -n – 在行号前面加上匹配行
  • head -n1 – 只打印第一行
  • cut -d : -f 1 – 使用:打印第一个剪切列:作为分隔符
  • 2>/dev/null – 如果$()表达式返回空,则发生沉默tail错误输出
  • grep -q – 沉默grep ,如果找到匹配,立即返回,因为我们只对退出代码感兴趣

这也应该工作?!

 perl -lpne 'print $ARGV if /abc.*?efg/s' file_list 

$ARGV在从换行符的file_list /s修饰符搜索中读取时包含当前文件的名称。

这应该工作:

 cat FILE | egrep 'abc|efg' 

如果有多个匹配,可以使用grep -v过滤掉