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


Answers:


225

Grep不足以执行此操作。

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

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

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

也有更新的pcre2grep。两者均由PCRE项目提供

pcre2grep可通过Mac Ports作为Mac OS X 在port的一部分使用pcre2

% sudo port install pcre2 

并通过Homebrew作为:

% brew install pcre

或pcre2

% brew install pcre2

pcre2grep是也可以在Linux(Ubuntu的18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE

11
@StevenLu- -M, --multiline允许模式匹配多行。
戒指承载者

7
请注意,。*(\ n |。)*等效于(\ n |。)*,后者更短。此外,在我的系统上,运行较长版本时会出现“ pcre_exec()错误-8”。因此,请改用'abc(\ n |。)* efg'!
daveagp

6
在这种情况下,您需要使表达式不贪婪:'abc.*(\n|.)*?efg'
环形承载者

4
并且您可以省略第一个.*-> 'abc(\n|.)*?efg'来使正则表达式更短(并且要学究)
Michi

6
pcregrep确实使事情变得容易,但grep也可以。例如,请参见stackoverflow.com/a/7167115/123695
Michael Mior

113

我不确定grep是否可能,但是sed使其非常容易:

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

4
这不会找到文件,而是从单个文件返回匹配的部分
shiggity 2014年

11
@Lj。请您能解释一下这个命令吗?我熟悉sed,但是如果以前从未见过这样的表达。
安东尼

1
@Anthony,记录在sed的手册页中的地址下。重要的是要意识到/ abc /&/ efg /是一个地址。
2014年

49
我怀疑如果有更多解释,这个答案可能会有所帮助,在这种情况下,我会再投票一次。我知道一些sed,但经过半个小时的摆弄后,还不足以使用此答案来生成有意义的退出代码。提示:正如您先前的评论所示,“ RTFM”很少在StackOverflow上获得投票。
Michael Scheper 2014年

25
通过示例快速解释:sed'1,5d':删除1到5之间的行。sed'1,5!d':删除不在1到5之间的行(即保持它们之间的行),而不是数字,您可以用/ pattern /搜索一行。另请参见以下更简单的代码:sed -n'/ abc /,/ efg /
p'p

86

这是受此答案启发的解决方案:

  • 如果'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,这意味着“。” 查找任何字符或换行符。


@syntaxerror不,我认为这只是小写字母l。AFAIK没有编号-1选项。
Sparhawk

毕竟您似乎是对的,也许我在测试时打错了字。无论如何,对不起您提出了错误的建议。
2014年

6
太好了 我对此只有一个问题。如果-z选项指定使用grep将换行符视为新行,zero byte characters那么为什么我们需要(?s)在正则表达式中使用?如果已经是非换行符,是否应该不能.直接将其匹配?
杜尔加·斯瓦鲁普

1
-z(aka --null-data)和(?s)正是将多行与标准grep匹配所需要的。使用MacOS的人员,请在系统上留下有关-z或--null-data选项的评论!
Zeke Fast

4
-z在MacOS上绝对不可用
Dylan Nicholson

33

sed足以满足上述LJ海报的要求,

代替!d,您只需使用p即可打印:

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

16

我严重依赖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。


14

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

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

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


1
这是我的方法,但是我正在使用\0,因此需要grep -a\x00... 上进行匹配……您已帮助我简化了工作!echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'现在是echo $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'
Charlie Gorichanaz

1
使用grep -o
kyb

7

awk一线:

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

4
abc如果文件中不存在结束模式,或者缺少最后一个结束模式,则将愉快地从头到尾打印。您可以解决该问题,但是它将使脚本非常复杂。
2013年

如何/efg/从输出中排除?
kyb

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

找到的第二个答案对于提取多行匹配的多行块很有用-必须使用非贪婪匹配(.*?)来获得最小匹配。
RichVel

5

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

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

不过,您需要注意如何执行此操作。您是否希望正则表达式匹配子字符串或整个单词?适当添加\ w标签。另外,虽然这完全符合您所说的示例,但是当abc在efg之后第二次出现时,它还是不起作用。如果要处理此问题,请在/ abc / case等中添加一个if。


3

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

grep搜索命名的输入文件(如果没有命名文件,或者如果给定单个连字符减号(-)作为文件名,则为标准输入),以查找包含与给定PATTERN匹配的


怎么样grep -Pz
Navaro

3

如果您愿意使用上下文,可以通过键入以下内容来实现

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

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


3

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

如果您还想使用正则表达式查找,也可以用grepcommand 替换command egrep


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上找到更多信息。


我认为第一个示例不起作用sift -lm 'abc.*efg' testfile,因为匹配很贪婪,并且吞噬了所有行,直到efg文件中的最后一行。
Alex RE博士

2

尽管sed选项是最简单,最简单的方法,但令人遗憾的是,LJ的单衬管并不是最轻便的。那些坚持使用C Shell版本的人将需要摆脱困境:

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

不幸的是,这在bash等人中无效。


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

1

如果您不热衷于模式顺序,可以使用grep。

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

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

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


1
但这将忽略文件中出现“ pattern1”和“ pattern2”的顺序,尽管-OP特别指定仅应匹配“ pattern1”之后出现“ pattern2”的文件。
Emil Lundberg

1

银搜索器

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

与无名小卒的答案类似,但用ag代替。银搜索者的速度优势可能会在这里大放异彩。


1
这似乎不起作用。(echo abctest; echo efg)|ag 'abc.*(\n|.)*efg'不匹配
phiresky '16

1

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

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • P用于基于perl的搜索
  • z用于以0字节而不是换行符结尾的行
  • o仅捕获匹配的内容,因为grep返回整行(在这种情况下,因为您做了-z,所以它是整个文件)。

正则表达式的核心是,[^>]它翻译为“不大于符号”


0

作为替代巴鲁磨憨的答案,有可能只能使用模式的顺序来执行grephead并且tail

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-在ith之后(包括首尾)打印所有行
  • grep -n -在匹配的行之前加上行号
  • head -n1 -仅打印第一行
  • cut -d : -f 1-使用:分隔符打印第一个剪切列
  • 2>/dev/null- tail如果$()表达式返回空,则会出现静音错误输出
  • grep -q-沉默grep并在找到匹配项后立即返回,因为我们只对退出代码感兴趣

谁能解释一下&>?我也在使用它,但是我从未在任何地方看到它的记录。顺便说一句,为什么我们实际上必须以这种方式使grep保持沉默?grep -q不会做这个把戏吗?
语法错误

1
&>告诉bash重定向标准输出和标准错误,请参见bash手册中的REDIRECTION。您说得很对,因为我们可以做的很好,grep -q ...而不是grep ... &>/dev/null抓住好收获!
Emil Lundberg 2014年

也这么觉得。将消除许多笨拙的额外键入的痛苦。感谢您的解释-因此我必须略过手册中的内容。(前段时间在其中查找了与远程相关的内容。)---您甚至可以考虑在答案中进行更改。:)
语法错误

0

这也应该工作吗?

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

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


0

文件*.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)行号。如果找到匹配项(测试-n ...),则找到efg的最后一个匹配项(查找全部并使用尾号-n 1获取最后一个匹配项)。

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

否则继续。

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

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

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

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


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等。


0

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

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

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


0

随着ugrep在几个月前发布:

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

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

请注意+?,除非您想将所有行匹配efg在一起直到efg文件中的最后一行,否则应使用惰性重复。


-3

这应该工作:

cat FILE | egrep 'abc|efg'

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


2
尽管此代码段是受欢迎的,并且可能会提供一些帮助,但是如果它包含有关如何以及为什么可以解决此问题的说明,则可以大大改善。请记住,您将来会为读者回答问题,而不仅仅是现在问的人!请编辑您的答案以添加说明,并指出适用的限制和假设。
Toby Speight

1
如问题所述,实际上并没有跨多行搜索。
n.st
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.