我想查找那些文件中按顺序包含字符串"abc"和"efg",并且这两个字符串在文件的不同行中出现。例如:一个包含以下内容的文件:
blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..
应该被匹配。
我想查找那些文件中按顺序包含字符串"abc"和"efg",并且这两个字符串在文件的不同行中出现。例如:一个包含以下内容的文件:
blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..
应该被匹配。
使用grep进行此操作有点棘手。
pcregrep是现代Linux系统中普遍存在的工具,可用作替代。
pcregrep -M 'abc.*(\n|.)*efg' test.txt
使用-M
或--multiline
选项可以使正则表达式匹配多行文本。
还有一个更新的pcre2grep,它们都是由PCRE项目提供的。
通过Mac Ports中的pcre2
端口,可以在Mac OS X上获得pcre2grep:
% 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
@StevenLu -M, --multiline
- 允许模式匹配多行文本。 - ring bearer'abc.*(\n|.)*?efg'
- ring bearer.*
-> 'abc(\n|.)*?efg'
,使正则表达式更短(并且更严谨)。 - Michipcregrep
确实可以让事情变得更容易,但 grep
也可以使用。例如,请参见 https://dev59.com/mnA65IYBdhLWcg3wogEb#7167115 - Michael Mior如果'abc'和'efg'可以在同一行上:
grep -zl 'abc.*efg' <你的文件列表>
如果'abc'和'efg'必须在不同的行上:
grep -Pzl '(?s)abc.*\n.*efg' <你的文件列表>
参数:
-P
使用Perl兼容的正则表达式(PCRE)
-z
将输入视为一组以零字节而不是换行符终止的行。即grep将输入视为一行。请注意,如果您不使用-l
,它将显示匹配项后跟一个NUL字符,请参阅注释
-l
仅列出匹配的文件名
(?s)
激活PCRE_DOTALL,这意味着'.'匹配任何字符或换行符
l
。据我所知,没有 -1
的选项。 - Sparhawk-z
选项指定grep将换行符视为“零字节字符”,那么为什么我们需要在正则表达式中使用(?s)
?如果它已经不是换行符,那么.
应该能够直接匹配它吧? - Durga Swaroop我不确定是否可以使用grep实现,但是sed非常容易:
sed -e '/abc/,/efg/!d' [file-with-content]
sed
,但从未见过这样的表达式。 - Anthony就像LJ在上面所说的,sed应该足以满足需求,
不需要使用!d,你可以直接使用p来打印:
sed -n '/abc/,/efg/p' file
我曾经非常依赖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。
首先使用tr
将换行符替换为其他字符,即可轻松完成此操作:
tr '\n' '\a' | grep -o 'abc.*def' | tr '\a' '\n'
在这里,我使用警报字符\a
(ASCII 7)代替换行符。
这在您的文本中几乎不会出现,grep
可以用.
匹配它,也可以用\a
特别匹配它。\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 Gorichanazgrep -o
. - kybawk一行命令:
awk '/abc/,/efg/' [file-with-content]
/efg/
? - kybgrep -A 500 abc test.txt | grep -B 500 efg
这将显示在距离不超过500行的范围内的“abc”和“efg”之间的所有内容,只要它们之间有内容。
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我不知道如何使用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/的情况下适当添加if语句。