我想找到有“abc”和“efg”的文件,这两个字符串在该文件中的不同行。一个包含以下内容的文件:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

应该匹配。


当前回答

我不知道如何用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。

其他回答

如果您对模式序列不感兴趣,可以使用grep。

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

例子

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

Grep -l将找到与第一个模式匹配的所有文件,xargs将为第二个模式查找Grep。希望这能有所帮助。

#!/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

使用ripgrep可以:

$ rg --multiline 'abc(\n|.)+?efg' test.txt
3:blah abc blah
4:blah abc blah
5:blah blah..
6:blah blah..
7:blah blah..
8:blah efg blah blah

或者其他咒语。

如果你愿意的话。作为换行符计算:

$ rg --multiline '(?s)abc.+?efg' test.txt
3:blah abc blah
4:blah abc blah
5:blah blah..
6:blah blah..
7:blah blah..
8:blah efg blah blah

或者等效于(?s)的是rg -multiline- multiline-dotall

为了回答最初的问题,它们必须在不同的行上:

$ rg——multiline 'abc.*[\n](\n|.)*efg' test.txt

如果你想让它“非贪婪”,这样你就不会用最后一个efg得到第一个abc(把它们分成成对):

$ rg——multiline 'abc.*[\n](\n|.)*?efg的用法

https://til.hashrocket.com/posts/9zneks2cbv-multiline-matches-with-ripgrep-rg

我非常依赖于pcregrep,但是对于更新的grep,您不需要安装它的许多特性。只需使用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。

作为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 -在匹配的行前加上行号 头-n1 -只打印第一行 Cut -d: -f 1 -打印第一个切割列,使用:作为分隔符 2>/dev/null -如果$()表达式返回空,则出现沉默尾部错误输出 Grep -q—关闭Grep并在找到匹配时立即返回,因为我们只对退出码感兴趣