270

I want to find files that have "abc" AND "efg" in that order, and those two strings are on different lines in that file. Eg: a file with content:

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

Should be matched.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Saobi
  • 16,121
  • 29
  • 71
  • 81
  • 4
    possible duplicate of [How can I search for a multiline pattern in a file?](http://stackoverflow.com/questions/152708/how-can-i-search-for-a-multiline-pattern-in-a-file) – Ciro Santilli OurBigBook.com Sep 15 '14 at 21:18
  • 1
    :) come to think of it .. in our world nothing stays the same over a period of time. So there may be better threads than this somewhere down the line – ring bearer Jul 15 '21 at 11:00

30 Answers30

277

Grep is an awkward tool for this operation.

pcregrep which is found in most of the modern Linux systems can be used as

pcregrep -M  'abc.*(\n|.)*efg' test.txt

where -M, --multiline allow patterns to match more than one line

There is a newer pcre2grep also. Both are provided by the PCRE project.

pcre2grep is available for Mac OS X via Mac Ports as part of port pcre2:

% sudo port install pcre2 

and via Homebrew as:

% brew install pcre

or for pcre2

% brew install pcre2

pcre2grep is also available on Linux (Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
ring bearer
  • 20,383
  • 7
  • 59
  • 72
  • 11
    @StevenLu `-M, --multiline` - Allow patterns to match more than one line. – ring bearer Jul 23 '12 at 13:55
  • 7
    Note that .*(\n|.)* is equivalent to (\n|.)* and the latter is shorter. Moreover on my system, "pcre_exec() error -8" occurs when I run the longer version. So try 'abc(\n|.)*efg' instead! – daveagp Feb 07 '13 at 00:52
  • This matched for me, from the line with `abc` to the last line with `efg`. how do I tell it to stop at the FIRST occurrence of `efg`? – Tom Klino Apr 09 '13 at 05:49
  • 7
    You need to make the expression non-greedy in that case example : `'abc.*(\n|.)*?efg'` – ring bearer Apr 09 '13 at 15:38
  • 5
    and you can omit the first `.*` -> `'abc(\n|.)*?efg'` to make the regex shorter (and to be pedantic) – Michi Aug 09 '13 at 10:56
  • If you experience problems, writing it like `([misc]|\n)*` helps. – redolent Sep 18 '13 at 22:36
  • TIL `pcregrep`. Thanks! – jkshah Oct 18 '13 at 09:57
  • 8
    `pcregrep` does make things easier, but `grep` will work too. For example, see http://stackoverflow.com/a/7167115/123695 – Michael Mior Apr 28 '14 at 18:56
  • It's also on Darwin by way of MacPorts' package `pcre`. – Philip Kearns May 17 '15 at 11:33
  • I have a file with multiple such patterns in a single file. I am getting error along with output: pcregrep: Too many errors - abandoned. pcregrep: Error -8, -21 or -27 means that a resource limit was exceeded. pcregrep: Check your regex for nested unlimited loops. – Krishna Pandey Jun 13 '16 at 06:20
  • Can you share the file someway? – ring bearer Jun 13 '16 at 07:23
  • My only problem with pcregrep after just having tried it is that it runs out of memory too quickly and just abandons the search. I had to manually set `buffer-size` (something ridiculous like `--buffer-size=1024000`) before it finally worked for me – smac89 Jan 26 '18 at 19:15
  • how to install it in centos? I didn't find useful information in google. – Lei Yang Dec 27 '18 at 14:21
  • On Linux, Ubuntu 18.04 `pcre2grep` is on [`pcre2-utils`](https://packages.ubuntu.com/bionic/amd64/pcre2-utils/filelist) – Pablo Bianchi Mar 11 '20 at 15:58
  • 1
    CentOS 7 provides the pcregrep utility under the pcre-tools package FYI – SI_ Jun 26 '21 at 00:33
  • @MichaelMior One should use pcre2grep to avoid "grep: memory exhausted" if using files larger than RAM. – user1133275 Apr 19 '23 at 09:24
134

Here is a solution inspired by this answer:

  • if 'abc' and 'efg' can be on the same line:

      grep -zl 'abc.*efg' <your list of files>
    
  • if 'abc' and 'efg' must be on different lines:

      grep -Pzl '(?s)abc.*\n.*efg' <your list of files>
    

Params:

  • -P Use perl compatible regular expressions (PCRE).

  • -z Treat the input as a set of lines, each terminated by a zero byte instead of a newline. i.e. grep treats the input as a one big line. Note that if you don't use -l it will display matches followed by a NUL char, see comments.

  • -l list matching filenames only.

  • (?s) activate PCRE_DOTALL, which means that '.' finds any character or newline.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
atti
  • 1,592
  • 1
  • 11
  • 10
  • @syntaxerror No, I think it's just a lower-case `l`. AFAIK there is no number `-1` option. – Sparhawk Oct 05 '14 at 01:02
  • Seems you're right after all, maybe I had made a typo when testing. In any case sorry for laying a false trail. – syntaxerror Oct 05 '14 at 03:59
  • 9
    This is excellent. I just have one question regarding this. If the `-z` options specifies grep to treat newlines as `zero byte characters` then why do we need the `(?s)` in the regex ? If it is already a non-newline character, shouldn't `.` be able to match it directly? – Durga Swaroop Feb 02 '16 at 06:33
  • 1
    -z (aka --null-data) and (?s) are exactly what you need to match multi-line with a standard grep. People on MacOS, please leave comments about availability of -z or --null-data options on your systems! – Zeke Fast May 21 '18 at 09:51
  • 9
    -z definitely not available on MacOS – Dylan Nicholson Jun 19 '18 at 03:18
  • Only this needs the two patterns to be in this specific order, right? (This could be fixed by using -E and 'abc.*efg|efg.*abc' .) – Kvothe Apr 26 '19 at 18:20
  • @DylanNicholson You could install GNU Grep on MacOS – JP Zhang Dec 10 '21 at 02:44
  • Beware that with this approach, if you try and view the match `grep -z` or `grep -zo` it appends a trailing null character. You can `tr` it away, see discussion https://stackoverflow.com/a/7167115/32453 but still a pain – rogerdpack Jan 20 '22 at 19:28
  • You can kind of get it in OS X `brew install grep` then use `ggrep` – rogerdpack Jan 20 '22 at 19:28
126

I'm not sure if it is possible with grep, but sed makes it very easy:

sed -e '/abc/,/efg/!d' [file-with-content]
timss
  • 9,982
  • 4
  • 34
  • 56
LJ.
  • 1,341
  • 1
  • 7
  • 2
  • 4
    This doesn't find files, it returns the matching part from a single file – shiggity Feb 14 '14 at 23:01
  • 15
    @Lj. please can you explain this command? I'm familiar with `sed`, but if have never seen such an expression before. – Anthony Apr 14 '14 at 12:55
  • 3
    @Anthony, It's documented in the man page of sed, under address. It's important to realise that /abc/ & /efg/ is an address. – Squidly May 29 '14 at 15:28
  • 66
    I suspect this answer would've been helpful if it had a bit more explanation, and in that case, I would've up-voted it one more time. I know a bit of sed, but not enough to use this answer to produce a meaningful exit code after half an hour of fiddling. Tip: 'RTFM' rarely gets up-votes on StackOverflow, as your previous comment shows. – Michael Scheper Jun 03 '14 at 07:20
  • 1
    I agree, an explanation would be nice :) – woojoo666 Oct 17 '14 at 13:00
  • 38
    Quick explanation by example: sed '1,5d' : delete lines between 1 and 5. sed '1,5!d' : delete lines not between 1 and 5 (i.e. keep the lines between) then instead of a number, you can search for a line with /pattern/. See also the simpler one below: sed -n '/abc/,/efg/p' p is for print and the -n flag don't display all lines – phil_w Aug 14 '15 at 14:47
  • This answer is incorrect; since you only use the first and last lines, it matches a lot more than the question asked for. – TamaMcGlinn Nov 21 '18 at 10:19
  • 1
    In my own words: the above command tells sed to delete all other lines than the ones in the range that can be found to hold "abc" at the beginning to the line containing "efg" at the end. !d is for reversing the match so d(elete) will delete the rest. /abc/,/efg/ is a range made up of two pattern searches. – elig Mar 09 '19 at 00:24
  • 1
    how to exclude `/efg/` from output? – kyb Jul 08 '19 at 16:25
  • `sed -e '/abc/,/efg/!d' | tail -n+2 | sed -e '$ d'` – Brad Parks Dec 13 '19 at 14:41
  • I found [this tutorial by Bruce Barnett](https://web.archive.org/web/20210425002429/https://www.grymoire.com/Unix/Sed.html#toc-uh-29) to be helpful in repurposing this idea for my own use. – chb May 01 '21 at 19:22
37

sed should suffice as poster LJ stated above,

instead of !d you can simply use p to print:

sed -n '/abc/,/efg/p' file
Kara
  • 6,115
  • 16
  • 50
  • 57
27

I relied heavily on pcregrep, but with newer grep you do not need to install pcregrep for many of its features. Just use grep -P.

In the example of the OP's question, I think the following options work nicely, with the second best matching how I understand the question:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

I copied the text as /tmp/test1 and deleted the 'g' and saved as /tmp/test2. Here is the output showing that the first shows the matched string and the second shows only the filename (typical -o is to show match and typical -l is to show only filename). Note that the 'z' is necessary for multiline and the '(.|\n)' means to match either 'anything other than newline' or 'newline' - i.e. anything:

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

To determine if your version is new enough, run man grep and see if something similar to this appears near the top:

   -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.

That is from GNU grep 2.10.

sage
  • 4,863
  • 2
  • 44
  • 47
19

This can be done easily by first using tr to replace the newlines with some other character:

tr '\n' '\a' | grep -o 'abc.*def' | tr '\a' '\n'

Here, I am using the alarm character, \a (ASCII 7) in place of a newline. This is almost never found in your text, and grep can match it with a ., or match it specifically with \a.

Gavin S. Yancey
  • 1,216
  • 1
  • 13
  • 34
  • 2
    This was my approach but I was using `\0` and thus needed `grep -a` and matching on `\x00`… You have helped me simplify! `echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'` is now `echo $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'` – Charlie Gorichanaz Mar 28 '17 at 21:11
  • 1
    Use `grep -o` . – kyb Jul 08 '19 at 16:28
9

awk one-liner:

awk '/abc/,/efg/' [file-with-content]
fedorqui
  • 275,237
  • 103
  • 548
  • 598
Swynndla
  • 99
  • 1
  • 2
  • 6
    This will happily print from `abc` through to end of file if the ending pattern is not present in the file, or the last ending pattern is missing. You can fix that but it will complicate the script rather significantly. – tripleee Mar 06 '13 at 08:01
  • How to exclude `/efg/` from output? – kyb Jul 08 '19 at 16:29
8

If you are willing to use contexts, this could be achieved by typing

grep -A 500 abc test.txt | grep -B 500 efg

This will display everything between "abc" and "efg", as long as they are within 500 lines of each other.

agouge
  • 147
  • 1
  • 3
6

You can do that very easily if you can use Perl.

perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

You can do that with a single regular expression too, but that involves taking the entire contents of the file into a single string, which might end up taking up too much memory with large files. For completeness, here is that method:

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
Sundar R
  • 13,776
  • 6
  • 49
  • 76
  • Found second answer was useful to extract a whole multi-line block with matches on a couple of lines - had to use non-greedy matching (``.*?``) to get minimal match. – RichVel Oct 09 '15 at 20:26
5

I don't know how I would do that with grep, but I would do something like this with awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

You need to be careful how you do this, though. Do you want the regex to match the substring or the entire word? add \w tags as appropriate. Also, while this strictly conforms to how you stated the example, it doesn't quite work when abc appears a second time after efg. If you want to handle that, add an if as appropriate in the /abc/ case etc.

Leo
  • 37,640
  • 8
  • 75
  • 100
frankc
  • 11,290
  • 4
  • 32
  • 49
4

Possible with 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

Or some other incantations.

If you want . to count as a newline:

$ 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

Or equivalent to having the (?s) would be rg --multiline --multiline-dotall

And to answer the original question, where they have to be on separate lines:

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

And if you want it "non greedy" so you don't just get the first abc with the last efg (separate them into pairs):

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

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

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
3

I released a grep alternative a few days ago that does support this directly, either via multiline matching or using conditions - hopefully it is useful for some people searching here. This is what the commands for the example would look like:

Multiline:

sift -lm 'abc.*efg' testfile

Conditions:

sift -l 'abc' testfile --followed-by 'efg'

You could also specify that 'efg' has to follow 'abc' within a certain number of lines:

sift -l 'abc' testfile --followed-within 5:'efg'

You can find more information on sift-tool.org.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
svent
  • 171
  • 1
  • I don't think the first example `sift -lm 'abc.*efg' testfile` works, because the match is greedy and gobbles up all lines until the last `efg` in the file. – Dr. Alex RE Jan 22 '20 at 19:47
3

If you need both words are close each other, for example no more than 3 lines, you can do this:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Same example but filtering only *.txt files:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

And also you can replace grep command with egrep command if you want also find with regular expressions.

Mariano Ruiz
  • 4,314
  • 2
  • 38
  • 34
2

Sadly, you can't. From the grep docs:

grep searches the named input FILEs (or standard input if no files are named, or if a single hyphen-minus (-) is given as file name) for lines containing a match to the given PATTERN.

Kaleb Pederson
  • 45,767
  • 19
  • 102
  • 147
2

With silver searcher:

ag 'abc.*(\n|.)*efg' your_filename

similar to ring bearer's answer, but with ag instead. Speed advantages of silver searcher could possibly shine here.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Shwaydogg
  • 2,499
  • 27
  • 28
2

While the sed option is the simplest and easiest, LJ's one-liner is sadly not the most portable. Those stuck with a version of the C Shell (instead of bash) will need to escape their bangs:

sed -e '/abc/,/efg/\!d' [file]

Which line unfortunately does not work in bash et al.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
bug
  • 904
  • 9
  • 11
1
#!/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
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
1

I used this to extract a fasta sequence from a multi fasta file using the -P option for grep:

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • P for perl based searches
  • z for making a line end in 0 bytes rather than newline char
  • o to just capture what matched since grep returns the whole line (which in this case since you did -z is the whole file).

The core of the regexp is the [^>] which translates to "not the greater than symbol"

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Jon Boyle
  • 11
  • 1
1

If you have some estimation about the distance between the 2 strings 'abc' and 'efg' you are looking for, you might use:

grep -r . -e 'abc' -A num1 -B num2 | grep 'efg'

That way, the first grep will return the line with the 'abc' plus #num1 lines after it, and #num2 lines after it, and the second grep will sift through all of those to get the 'efg'. Then you'll know at which files they appear together.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
1

With ugrep released a few months ago:

ugrep 'abc(\n|.)+?efg'

This tool is highly optimized for speed. It's also GNU/BSD/PCRE-grep compatible.

Note that we should use a lazy repetition +?, unless you want to match all lines with efg together until the last efg in the file.

Dr. Alex RE
  • 1,772
  • 1
  • 15
  • 23
1

You have at least a couple options --

  1. DOTALL method
  • use (?s) to DOTALL the . character to include \n
  • you can also use a lookahead (?=\n) -- won't be captured in match

example-text:

true
match me

false
match me one

false
match me two

true
match me three
third line!!
{BLANK_LINE}

command:

grep -Pozi '(?s)true.+?\n(?=\n)' example-text

-p for perl regular expressions

-o to only match pattern, not whole line

-z to allow line breaks

-i makes case-insensitive

output:

true                                                  
match me                                              
true                                                  
match me three                                        
third line!!

notes:

- +? makes modifier non-greedy so matches shortest string instead of largest (prevents from returning one match containing entire text)
  1. you can use the oldschool O.G. manual method using \n

command:

grep -Pozi 'true(.|\n)+?\n(?=\n)'

output:

true                                                  
match me                                              
true                                                  
match me three                                        
third line!!
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
1

you can use grep incase you are not keen in the sequence of the pattern.

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

example

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

grep -l will find all the files which matches the first pattern, and xargs will grep for the second pattern. Hope this helps.

Taryn
  • 242,637
  • 56
  • 362
  • 405
Balu Mohan
  • 27
  • 1
  • 2
    That would ignore the order "pattern1" and "pattern2" appear in the file, though - OP specifically specifies that only files where "pattern2" appears AFTER "pattern1" should be matched. – Emil Lundberg Aug 02 '13 at 07:58
0

As an alternative to Balu Mohan's answer, it is possible to enforce the order of the patterns using only grep, head and 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

This one isn't very pretty, though. Formatted more readably:

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

This will print the names of all files where "pattern2" appears after "pattern1", or where both appear on the same line:

$ 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

Explanation

  • tail -n +i - print all lines after the ith, inclusive
  • grep -n - prepend matching lines with their line numbers
  • head -n1 - print only the first row
  • cut -d : -f 1 - print the first cut column using : as the delimiter
  • 2>/dev/null - silence tail error output that occurs if the $() expression returns empty
  • grep -q - silence grep and return immediately if a match is found, since we are only interested in the exit code
Emil Lundberg
  • 7,268
  • 6
  • 37
  • 53
  • Can anyone please explain the `&>`? I'm using it too, but I never saw it documented anywhere. BTW, why do we have to silence grep that way, actually? `grep -q` won't do the trick as well? – syntaxerror Sep 23 '14 at 04:23
  • 1
    `&>` tells bash to redirect both standard output and standard error, see REDIRECTION in the bash manual. You're very right in that we could just as well do `grep -q ...` instead of `grep ... &>/dev/null`, good catch! – Emil Lundberg Sep 23 '14 at 07:18
  • Thought so. Will take away the pain of lots of awkward extra typing. Thanks for the explanation - so I must have skipped a bit in the manual. (Looked up something remotely related in it some time ago.)---You might even consider changing it in your answer.:) – syntaxerror Sep 24 '14 at 00:31
0

This should work too?!

perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGV contains the name of the current file when reading from file_list /s modifier searches across newline.

bastelflp
  • 9,362
  • 7
  • 32
  • 67
PS12
  • 31
  • 5
0

The filepattern *.sh is important to prevent directories to be inspected. Of course some test could prevent that too.

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

The

grep -n -m1 abc $f 

searches maximum 1 matching and returns (-n) the linenumber. If a match was found (test -n ...) find the last match of efg (find all and take the last with tail -n 1).

z=$( grep -n efg $f | tail -n 1)

else continue.

Since the result is something like 18:foofile.sh String alf="abc"; we need to cut away from ":" till end of line.

((${z/:*/}-${a/:*/}))

Should return a positive result if the last match of the 2nd expression is past the first match of the first.

Then we report the filename echo $f.

user unknown
  • 35,537
  • 11
  • 75
  • 121
0

Here's a way by using two greps in a row:

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

returns 0 or a positive integer.

egrep -o (Only shows matches, trick: multiple matches on the same line produce multi-line output as if they are on different lines)

  • grep -A1 abc (print abc and the line after it)

  • grep efg | wc -l (0-n count of efg lines found after abc on the same or following lines, result can be used in an 'if")

  • grep can be changed to egrep etc. if pattern matching is needed

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
kevins
  • 1
0

To search recursively across all files (across multiple lines within each file) with BOTH strings present (i.e. string1 and string2 on different lines and both present in same file):

grep -r -l 'string1' * > tmp; while read p; do grep -l 'string2' $p; done < tmp; rm tmp 

To search recursively across all files (across multiple lines within each file) with EITHER string present (i.e. string1 and string2 on different lines and either present in same file):

grep -r -l 'string1\|string2' * 
0

I believe the following should work and has the advantage of only using extended regular expressions without the need to install an extra tool like pcregrep if you don’t have it yet or don’t have the -P option to grep available (eg. macOS):

egrep -irzo “.*abc(.*\s.*){1,}.*efg.*" path_to_filenames

Caveat emptor: this does some slight disadvantages:

  • it will find the largest selection of lines from the first abc to the last efg in each file, unless...
  • there are several repetitions of the abc [stuff] efg pattern in each file.
TransferOrbit
  • 201
  • 2
  • 7
0

Using any awk and only reading 1 line at a time into memory:

$ awk 'f && /efg/{print FILENAME; exit} /abc/{f=1}' file
file

Obviously you could change it to print whatever you like, I'm just guessing you want the name of the file printed.

If you want a succ/fail exit status like you'd get from grep then tweak it to:

awk 'f && /efg/{f++; exit} /abc/{f=1} END{ if (f==2) { print FILENAME; exit 0 } else exit 1 }' file

or if you want to handle multiple input files and your awk supports nextfile:

awk 'FNR==1{f=0} f && /efg/{print FILENAME; nextfile} /abc/{f=1}' file1 file2 ...

etc...

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
-3

This should work:

cat FILE | egrep 'abc|efg'

If there is more than one match you can filter out using grep -v

arghtype
  • 4,376
  • 11
  • 45
  • 60
Guru
  • 9
  • 3
    Whilst this code snippet is welcome, and may provide some help, it would be [greatly improved if it included an explanation](//meta.stackexchange.com/q/114762) of *how* and *why* this solves the problem. Remember that you are answering the question for readers in the future, not just the person asking now! Please [edit] your answer to add explanation, and give an indication of what limitations and assumptions apply. – Toby Speight Mar 09 '17 at 11:24
  • 1
    That doesn't actually search across *multiple lines*, as stated in the question. – n.st Mar 09 '17 at 16:52