如何跨多行“ grep”模式?


24

看来我在滥用grep/ egrep

我试图在多行中搜索字符串,但找不到匹配项,但我知道我要查找的内容应该匹配。最初我以为我的正则表达式是错误的,但最终我读到这些工具是按行运行的(而且我的正则表达式太琐碎了,不可能成为问题)。

那么,将使用哪种工具来搜索多行模式?



1
@CiroSantilli-我不认为此Q和您链接到的Q是重复的。另一个问是问您如何进行多行模式匹配(即,我应该/可以使用哪种工具进行此操作),而另一个问是如何使用grep。国际海事组织,它们是紧密相关的,但不是虚假的。
slm

@sim这些情况很难决定:我明白你的意思。我认为这种特殊情况最好作为重复项使用,因为用户说"grep"建议动词“ to grep”,并且最常见的答案(包括已接受的答案)不要使用grep。
Ciro Santilli新疆改造中心法轮功六四事件

Answers:


24

这是sed一个可以grep在多行中为您提供类似行为的工具:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

怎么运行的

  • -n 禁止打印每行的默认行为
  • /foo/{}指示它进行匹配foo,并在弯曲线内进行匹配行的操作。替换foo为图案的开始部分。
  • :start 是一个分支标签,可帮助我们不断循环直到找到正则表达式的结尾。
  • /bar/!{}将对不匹配的行执行弯曲的内容bar。替换bar为图案的结尾部分。
  • N将下一行追加到活动缓冲区(sed将此称为模式空间)
  • b startstart只要模式空间不包含,它将无条件地分支到我们先前创建的标签,以便继续追加下一行bar
  • /your_regex/p如果匹配则打印模式空间your_regex。您应该用your_regex要在多行中匹配的整个表达式替换。

1
+1将此添加到工具中!谢谢。
wmorrison365 '18

注意:在MacOS上,这给sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
获取sed: unterminated {错误
Nomaed

@Nomaed在这里暗中射击,但是您的正则表达式碰巧包含任何“ {”字符吗?如果是这样,则需要反斜杠转义它们。
约瑟夫·R。

1
@Nomaed似乎与实现之间的差异有关sed。我试图遵循该答案中的建议以使上述脚本符合标准,但它告诉我“开始”是未定义的标签。因此,我不确定是否可以通过符合标准的方式来完成此操作。如果您要管理它,请随时编辑我的答案。
约瑟夫·R。

19

我通常使用一种称为的工具pcregrep,可以使用yum或将其安装在大多数linux版本中apt

例如。

假设您有一个名为testfilecontent 的文件

abc blah
blah blah
def blah
blah blah

您可以运行以下命令:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

跨多行进行模式匹配。

此外,您也可以这样做sed

$ sed -e '/abc/,/def/!d' testfile

5

这是使用Perl的一种更简单的方法:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

或者(因为JosephR 接过sed路线,我会无耻地窃取他的建议

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

说明

$f=join("",<>);:这将读取整个文件,并将其内容(换行符和全部)保存到变量中$f。然后foo\nbar.*\n,我们尝试匹配,如果匹配则将其打印出来(特殊变量$&保存找到的最后一个匹配项)。的///m需要,使整个新行的正则表达式匹配。

-0设置输入记录分隔符。将其设置为00激活“段落模式”,其中Perl将使用连续的换行符(\n\n)作为记录分隔符。如果没有连续的换行符,则会一次读取(提取)整个文件。

警告:

难道不是大文件做到这一点,将整个文件加载到内存中,并且可能是一个问题。


2

一种方法是使用Perl。例如,这是一个名为的文件的内容foo

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

现在,这是一些Perl,它将与以foo开头的任何行以及以bar开头的任何行匹配:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl,细分为:

  • while(<>){$all .= $_} 这会将整个标准输入加载到变量中 $all
  • while($all =~虽然变量all具有正则表达式...
  • /^(foo[^\n]*\nbar[^\n]*\n)/m正则表达式:foo在行的开头,后跟任意数量的非换行符,然后是换行符,紧接着是“ bar”,其余的行中都带有bar。/m正则表达式末尾的意思是“跨多行匹配”
  • print $1 打印正则表达式中带括号的部分(在本例中为整个正则表达式)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m 删除正则表达式的第一个匹配项,因此我们可以匹配相关文件中多个正则表达式的情况

并输出:

foo line 1
bar line 2
foo
bar line 6

3
只是说您的Perl可以简化为更惯用:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

grep替代筛选器支持多行匹配(免责声明:我是作者)。

假设testfile包含:

<书>
  <title>催眠药</ title>
  <描述> Lorem ipsum dolor坐在amet,consectetur
  忠实的精英,圣贤时代
  Laboure et dolore magna aliqua </ description>
</ book>


sift -m '<description>.*?</description>' (显示包含描述的行)

结果:

测试文件:<描述> Lorem ipsum dolor坐在amet,安全
测试文件:adipiscing elit,sed do eiusmod tempor incididunt ut
测试文件:labour et dolore magna aliqua </ description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (提取并重新格式化说明)

结果:

description =“ Lorem ipsum dolor sit amet,consectetur
  忠实的精英,圣贤时代
  劳动与dolore magna aliqua”

1
非常好的工具。恭喜你!尝试将其包含在Ubuntu之类的发行版中。
Lourenco

2

只需一个支持Perl-regexp参数的普通grep即可P完成此工作。

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) 称为DOTALL修饰符,它使正则表达式中的点不仅与字符匹配,而且与换行符匹配。


当我尝试此解决方案时,输出不会以'def'结尾,而是到达文件'blah'的结尾
弯弯的

也许您的grep不支持该-P选项
Avinash Raj

1

我使用grep和-A选项以及另一个grep为我解决了这个问题。

grep first_line_word -A 1 testfile | grep second_line_word

-A 1选项在找到的行之后打印1行。当然,这取决于您的文件和单词组合。但是对我来说,这是最快,最可靠的解决方案。


别名grepp ='grep --color = auto -B10 -A20 -i',然后输入somefile | grepp blah | grepp foo | grepp bar ...是的-A和-B非常方便...您有最佳答案
Scott Stensland

1

假设我们有一个包含以下内容的文件test.txt

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

可以使用以下代码:

sed -n '/foo/,/bar/p' test.txt

对于以下输出:

foo
here
is the
text
to keep between the 2 patterns
bar

1

如果我们想获得两个模式之间的文本(不包括它们自己)。

假设我们有一个包含以下内容的文件test.txt

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

可以使用以下代码:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

对于以下输出:

here
is the
text
to keep between the 2 patterns

它是如何工作的,让我们逐步进行

  1. /foo/{ 当行包含“ foo”时被触发
  2. n 用下一行替换模式空间,即单词“ here”
  3. b gotoloop 转到标签“ gotoloop”
  4. :gotoloop 定义标签“ gotoloop”
  5. /bar/!{ 如果模式不包含“ bar”
  6. h 用图案替换容纳空间,因此“此处”保存在容纳空间中
  7. b loop 分支到标签“ loop”
  8. :loop 定义标签“循环”
  9. N 将图案附加到容纳空间。
    现在容纳空间包含:
    “这里”
    “是”
  10. :gotoloop 我们现在进入第4步,循环直到一行包含“ bar”
  11. /bar/ 循环结束,找到“条”,它是模式空间
  12. g 模式空间被包含在主循环中已保存的“ foo”和“ bar”之间的所有行的保留空间替换
  13. p 将模式空间复制到标准输出

完成!


做得好,+ 1。我通常通过将换行符插入SOH并执行常规的sed命令,然后替换换行符来避免使用这些命令。
A.Danischewski
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.