如何使用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的答案的替代,可以仅使用grep
, head
和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
– 打印第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过滤掉