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

时间:2010-04-21 19:58:40

标签: regex grep

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

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

应该匹配。

26 个答案:

答案 0 :(得分:191)

Grep不足以进行此操作。

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

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

其中-M, - multiline允许模式匹配多行

还有一个较新的pcre2grep。两者都由PCRE project提供。

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

% sudo port install pcre2 

并通过Homebrew作为:

% brew install pcre

或pcre2

% brew install pcre2

答案 1 :(得分:105)

我不确定grep是否可行,但是sed使它非常简单:

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

答案 2 :(得分:71)

这是一个受this answer启发的解决方案:

  • 如果' abc'和' efg'可以在同一条线上:

    grep -zl 'abc.*efg' <your list of files>
    
  • 如果&#39; abc&#39;和&#39; efg&#39;必须在不同的路线上:

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

PARAMS:

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

  • -l打印通常打印输出的每个输入文件的名称。

  • (?s)激活PCRE_DOTALL,这意味着&#39;。&#39;找到任何字符或换行符。

答案 3 :(得分:29)

sed应该足够像LJ上面提到的那样,

而不是!d你可以简单地用p来打印:

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

答案 4 :(得分:12)

我非常依赖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。

答案 5 :(得分:9)

首先使用tr将换行符替换为其他字符,可以轻松完成此操作:

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

在这里,我使用警报字符\a(ASCII 7)代替换行符。 这几乎从未在您的文字中找到,grep可以将其与.匹配,或者与\a专门匹配。

答案 6 :(得分:6)

如果你可以使用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

答案 7 :(得分:5)

awk one-liner:

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

答案 8 :(得分:5)

我不知道怎么用grep会这样做,但我会用awk做这样的事情:

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

但是你需要注意如何做到这一点。你想要正则表达式匹配子字符串或整个单词吗?根据需要添加\ w标签。此外,虽然这严格符合您所说的示例,但在ecg之后第二次出现abc时,它并不能正常工作。如果你想处理它,在/ abc / case等中添加一个if。

答案 9 :(得分:3)

可悲的是,你做不到。来自grep文档:

  

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

答案 10 :(得分:3)

我几天前发布了一个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找到更多信息。

答案 11 :(得分:2)

虽然sed选项是最简单和最简单的,但是LJ的单行可能不是最便携的。那些坚持使用C Shell版本的人需要逃避他们的爆炸:

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

遗憾的是,这不适用于bash等人。

答案 12 :(得分:2)

如果您愿意使用上下文,可以通过输入

来实现
grep -A 500 abc test.txt | grep -B 500 efg

这将显示“abc”和“efg”之间的所有内容,只要它们彼此相距500行。

答案 13 :(得分:2)

如果你需要两个单词彼此靠近,例如不超过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"

如果您还希望使用正则表达式查找,还可以使用grep命令替换egrep命令。

答案 14 :(得分:1)

使用silver searcher

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

类似于戒指持有者的答案,但用ag代替。银色搜索者的速度优势可能会在这里闪耀。

答案 15 :(得分:1)

我使用它来使用grep的-P选项从多个fasta文件中提取fasta序列:

grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta

-P用于基于Perl的搜索 -z用于以0字节而不是换行符结尾的行 -o仅捕获匹配的内容,因为grep返回整行(在这种情况下,因为您这样做了-z是整个文件)。 正则表达式的核心是[^>],它翻译为“不大于符号”

答案 16 :(得分:1)

你可以使用grep,因为你不喜欢模式的顺序。

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

例如

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

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

答案 17 :(得分:1)

#!/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

答案 18 :(得分:0)

作为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并在找到匹配后立即返回,因为我们只对退出代码感兴趣

答案 19 :(得分:0)

这也应该有用吗?!

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

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

答案 20 :(得分:0)

filepattern *.sh对于防止检查目录很重要。当然,一些测试也可以防止这种情况发生。

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

grep -n -m1 abc $f 

搜索最多1个匹配并返回(-n)亚麻。 如果找到了匹配项(test -n ...),找到efg的最后一个匹配项(找到所有并使用尾部-n 1取最后一个)。

z=$( grep -n efg $f | tail -n 1)

否则继续。

由于结果类似于18:foofile.sh String alf="abc";,我们需要切断“:”直到行尾。

((${z/:*/}-${a/:*/}))

如果第二个表达式的最后一个匹配项超过第一个表达式的第一个匹配项,则应返回正结果。

然后我们报告文件名echo $f

答案 21 :(得分:0)

如果您对要查找的两个字符串“ abc”和“ efg”之间的距离有一些估计,则可以使用:

  
    

grep -r。 -e'abc'-A num1 -B num2 | grep'efg'

  

这样,第一个grep将返回后跟'abc'加上后#num1行和后#num2行的行,第二个grep将筛选所有这些行以获取'efg'。 然后,您将知道它们一起出现在哪些文件中。

答案 22 :(得分:0)

为什么不像这样简单?

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

返回0或正整数。

egrep -o(仅显示匹配项,技巧:同一行上的多个匹配项会产生多行输出,就像它们在不同行上一样)

  • grep -A1 abc(打印abc及其后一行)

  • grep efg | wc -l(在abc之后的同一行或后续行中发现的efg行为0-n,结果可以在'if“中使用)

  • 如果需要模式匹配,
  • grep可以更改为egrep等。

答案 23 :(得分:0)

几个月前发布了ugrep

ugrep 'abc(\n|.)+?efg'

此工具针对速度进行了高度优化。它还与GNU / BSD / PCRE-grep兼容。

请注意,除非您想将所有与+?匹配的行一起匹配到文件中的最后efg,否则我们应该使用延迟重复efg

答案 24 :(得分:0)

要对所有文件(每个文件中的多行)进行递归搜索,同时存在两个字符串(即,字符串1和字符串2在不同的行上,并且都在同一文件中):

grep -r -l 'string1' * > tmp; while read p; do grep -l 'string2' $p; done < tmp; rm tmp 

要在所有文件中(每个文件中的多行之间)以递归方式搜索存在EITHER字符串的字符串(即,string1和string2位于不同的行中,并且都出现在同一文件中):

grep -r -l 'string1\|string2' * 

答案 25 :(得分:-3)

这应该有效:

cat FILE | egrep 'abc|efg'

如果有多个匹配,您可以使用grep -v

过滤掉