如何使用grep查找多行的模式?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用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.*(
|.)*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
可悲的是,你做不到。来自grep
文档:
对于包含与给定PATTERN匹配的行,grep搜索命名输入FILE(或标准输入,如果没有文件被命名,或者如果单个连字符 - ( - )作为文件名)。
我几天前发布了一个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等人。
如果您愿意使用上下文,可以通过键入来实现
grep -A 500 abc test.txt | grep -B 500 efg
这将显示“abc”和“efg”之间的所有内容,只要它们彼此相差500行即可。
如果你需要两个单词彼此靠近,例如不超过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
命令。
#!/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。希望这可以帮助。
ag 'abc.*(
|.)*efg'
类似于持票人的回答,但用ag代替。银色搜索者的速度优势可能会在这里闪耀。
我使用它来使用grep的-P选项从多个fasta文件中提取fasta序列:
grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta
-P用于基于perl的搜索-z使得行结束为0字节而不是换行符char -o以捕获匹配的内容,因为grep返回整行(在这种情况下,因为你做了-z是整个文件)。正则表达式的核心是[^>]
,转换为“不大于符号”
作为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
th之后打印所有行,包括在内grep -n
- 在匹配的行前加上行号head -n1
- 仅打印第一行cut -d : -f 1
- 使用:
作为分隔符打印第一个切割列2>/dev/null
- 如果tail
表达式返回空,则会出现$()
错误输出grep -q
- 沉默grep
并在找到匹配时立即返回,因为我们只对退出代码感兴趣
我不确定grep是否可行,但是sed使它非常简单:
sed -e '/abc/,/efg/!d' [file-with-content]
这也应该有用吗?!
perl -lpne 'print $ARGV if /abc.*?efg/s' file_list
$ARGV
包含从换行符中的file_list
/s
修饰符搜索时当前文件的名称。
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)亚麻布。如果找到匹配(测试-n ...),找到efg的最后一个匹配(找到所有并使用尾部-n 1取最后一个)。
z=$( grep -n efg $f | tail -n 1)
否则继续。
由于结果类似于18:foofile.sh String alf="abc";
,我们需要从“:”切断直到行尾。
((${z/:*/}-${a/:*/}))
如果第二个表达式的最后一个匹配超过第一个表达式的第一个匹配,则应返回正结果。
然后我们报告文件名echo $f
。
如果您对要搜索的2个字符串'abc'和'efg'之间的距离有所估计,您可以使用:
grep -r。 -e'abc'-A num1 -B num2 | grep'efg'
这样,第一个grep将返回行后面带有'abc'加#num1行的行,然后返回#num2行,第二个grep将筛选所有这些行以获得'efg'。然后你就会知道它们一起出现在哪些文件中。
这应该工作:
cat FILE | egrep 'abc|efg'
如果有多个匹配,您可以使用grep -v过滤掉
这是一个受this answer启发的解决方案:
- 如果'abc'和'efg'可以在同一行:
grep -zl 'abc.*efg' <your list of files>
- 如果'abc'和'efg'必须在不同的行上:
grep -Pzl '(?s)abc.* .*efg' <your list of files>
PARAMS:
-z
将输入视为一组行,每行以零字节而不是换行符结束。即grep将输入威胁为一条大线。-l
打印通常打印输出的每个输入文件的名称。(?s)
激活PCRE_DOTALL,这意味着'。'找到任何字符或换行符。
sed应该足够像上面提到的海报LJ,
而不是!d你可以简单地使用p来打印:
sed -n '/abc/,/efg/p' file
我非常依赖pcregrep,但是对于更新的grep,你不需要为它的许多功能安装pcregrep。只需使用grep -P
。
在OP的问题的例子中,我认为以下选项很好地工作,第二个最佳匹配我如何理解这个问题:
grep -Pzo "abc(.|
)*efg" /tmp/tes*
grep -Pzl "abc(.|
)*efg" /tmp/tes*
我将文本复制为/ tmp / test1并删除了'g'并保存为/ tmp / test2。这是输出显示第一个显示匹配的字符串,第二个显示只有文件名(典型的-o是显示匹配,典型的-l是仅显示文件名)。请注意,'z'对于多行是必要的,'(。| n)'表示匹配'换行符以外的任何内容'或'换行符' - 即任何东西:
user@host:~$ grep -Pzo "abc(.|
)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@host:~$ grep -Pzl "abc(.|
)*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查找多行的模式?的主要内容,如果未能解决你的问题,请参考以下文章
使用 grep 和 sed 在 shell 中查找和替换同一文件中的多行