5

I know that multiline regexp has been discussed dozens of times but I just can't get it to work with my pattern.

I'll try to explain. I have some text files in a directory. Example of text in a file:

LINE OF TEXT 2
LINE OF TEXT 1
LINE OF TEXT 3

LINE OF TEXT 1
LINE OF TEXT 2
LINE OF TEXT 3

LINE OF TEXT 1
LINE OF TEXT 3

LINE OF TEXT 3
LINE OF TEXT 2
LINE OF TEXT 1

LINE OF TEXT 2
LINE OF TEXT 3

I want to find "LINE OF TEXT 3" which comes after "LINE OF TEXT 2" which in turn comes after "LINE OF TEXT 1" (with no empty lines in between).

Each line must be a regexp itself, for example a line starts with "LINE" and ends with a particular number.

Note: Not all files contain that exact line sequence, so if a pattern match then don't print the pattern but just print the filename to STDOUT.

Can this be done in a one-liner regexp? So, for example, awk searches a pattern in a file and prints filename to STDOUT if a pattern found. I then can use this regexp in a combination with "find -exec".

Any mentioned tool will go (grep, awk, sed or perl).

2
  • awk searches a pattern in a file and prints filename to STDOUT if a pattern found. awk '/pattern/{print FILENAME;exit}' logfile i am still not clear about the LINE 3,2,1...etc logic.
    – Kamaraj
    Feb 23, 2017 at 6:42
  • I've added an answer using Awk, but please note that your actual data may indicate a slightly different approach. Showing dummy text that more closely matches your actual text would improve the potential quality of the answers.
    – Wildcard
    Feb 23, 2017 at 7:04

3 Answers 3

4

You can do this with Awk by setting the "Record Separator" variable to be a regex matching at least two consecutive newline characters:

awk -v RS='\n\n+' '/1.*2.*3/' file.txt

You can also set the "Field Separator" to be a single newline character:

awk -v RS='\n\n+' -F '\n' '$1 == "LINE OF TEXT 1" && $2 == "LINE OF TEXT 2" && $3 == "LINE OF TEXT 3"' file.txt

Broken up for readability:

awk -v RS='\n\n+' -F '\n' '
  $1 == "LINE OF TEXT 1" &&
  $2 == "LINE OF TEXT 2" &&
  $3 == "LINE OF TEXT 3"
' file.txt

With your requirement of only printing the filename if a match is found, you can do this like so:

awk -v RS='\n\n+' -F '\n' '
  $1 == "LINE OF TEXT 1" &&
  $2 == "LINE OF TEXT 2" &&
  $3 == "LINE OF TEXT 3" {
    match++
  }
  END {
    if (match) {
      print FILENAME
    }
' file.txt

But considering you are talking about using find in combination with awk, I'd recommend just using Awk for the exit status and using find for the printing:

find . -type f -exec awk -v RS='\n\n+' -F '\n' '
  $1 ~ /LINE OF TEXT 1/ &&
  $2 ~ /LINE OF TEXT 2/ &&
  $3 ~ /LINE OF TEXT 3/ {
    exit 0
  }
  END { exit 1 }
' {} \; -print

That way, if you want to do something else before printing (some other find primary), you're already set up to do so.

1
  • Thank you for such a detailed answer! This also works. The last part is most useful because I can use a pattern as a regexp and put a /LINE.*1/ there. In case of double quotes a regexp won't work for me.
    – MikZyth
    Feb 23, 2017 at 14:02
1

You can use the "paragraph mode" in Perl, it will read the file by the blocks separated by multiple newlines. Just set the empty string to the input record separator $/:

perl -lne 'BEGIN { $/ = "" }
       $found = 1 if /^LINE.* 1\nLINE.* 2\nLINE.* 3$/m;
       if (eof) { print $ARGV if $found; undef $found }
' -- file1 file2...
  • eof is true at each file's end
  • $ARGV is the name of the currently opened file.
0
0

You can do this using a find<->perl duo working in tandem, like :

find . -type f -exec \
  perl -l -0777ne '/^LINE.* 1\nLINE.* 2\nLINE.* 3$/m && print $ARGV' {} +
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .