4

This question already has an answer here:

I have a file which contains multiple occurrences of pattern "====". These patterns are followed by texts. I'd like to search for the last occurrence of the "====" pattern and print all the lines after the pattern. Any ideas on how to do it in bash? Combination of grep, awk, sed or tail, etc..?

Sample file:

====

First run of script

End of first Script

====

2nd run of script

End of 2nd script

====

3rd run of script

End of 3rd script

Output of the command should look like below.

3rd run of script

End of 3rd script

marked as duplicate by don_crissti, RalfFriedl, Jeff Schaller, thrig, Thomas Sep 9 '18 at 15:36

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    Thank you @l0b0 for posting the link. Sorry for the duplicate question. I had done many searches for a possible solution and the link you provided matches my exact needs. – user612223 Sep 9 '18 at 4:13
  • 1
    No problem at all; cross-site duplicates aren't a thing, and a question being duplicate doesn't mean it's bad. – l0b0 Sep 9 '18 at 4:22
  • Welcome , You can vote up or accept the answer which solve your problem instead of editing your question. – GAD3R Sep 9 '18 at 11:20
5

Software tools method, more efficient for big files since it only reads the end of the file, then stops when it finds the last match:

tac sample.txt | grep -F -m1 -B 999999 '====' | head -n -1 | tac

Note: increase or reduce 999999 as needed, just so it's longer than any possible match. This code would work best if it's known in advance that the last match is known to be near the end of a large file. See also Glenn Jackman's answer with variants for awk and sed which avoid the need for 999999. For systems with very low resources, grep is more efficient than awk or sed.

To avoid guessing, some added stat ugliness would work:

f=sample.txt; tac "$f" | grep -F -m1 -B $(stat -c '%s' "$f") '====' |
head -n -1 | tac
4

Based on SO duplicate:

$ sed -n '/====/h;//!H;$!d;x;//p' sample.txt 
====

3rd run of script

End of 3rd script

Output of the command should look like below.

3rd run of script

End of 3rd script

I started writing an explanation, but I don't actually understand some of the commands so I'll just refer to info sed.

  • (Let's see if I got this right) /====/h sets the hold buffer to the current line if it matches ====; //!H adds it to the hold buffer if it doesn't match ====. Those combine to clear the hold on a ==== and to store any lines in there. Then $!d deletes the current line (it's already stored in the hold) and jumps to the next, unless this was the last line. The x and p only run on the last line: x loads the hold buffer to the active buffer, and //p prints it if it contains a ====. (The // pattern means to use the previous pattern, so it's just a shorthand for /====/.) – ilkkachu Sep 9 '18 at 12:19
  • The final condition on the p means that if ==== isn't seen, this won't print anything. But this also will print the ==== line itself. – ilkkachu Sep 9 '18 at 12:21
1

save the text in file.txt and run this command:

awk 'p; /====/ $p' file.txt | tail -3

output:

3rd run of script

End of 3rd script
  • Your awk command doesn't do anything... You may as well run only tail -3 (which is obsolete - you should use tail with -n). Either way, this only works with the OP input sample, that is it only prints the last 3 lines, regardless. I guess the 3 people who mechanically upvoted here don't care about that. – don_crissti Sep 9 '18 at 11:46
  • With p unset, the first p is a falsy condition, and doesn't do anything. The second is the same as /====/ $0 when p gets coerced to an integer, but what in the world is the concatenation of a regex and a field (or string) supposed to be? It does seem to act like a true condition, though, so it looks like that's the same as running awk 1. Or cat. – ilkkachu Sep 9 '18 at 11:53
1

Same concept as agc's answer, but doesn't require you to guess how many lines there might be:

with GNU sed

tac file | sed '/====/Q' | tac

or awk

tac file | awk '/====/ {exit} 1' | tac

The idea of the double tac is to invert the question: How to print all lines preceding the first occurrence of pattern. That is a much simpler problem.

0

In awk, something like this:

awk '/====/ { t = ""; next } { t = t $0 "\n"; } END { printf "%s", t; }' < sample.txt

It stores the input lines in t, and clears t when it sees ====. What ever is in there is printed at the end.

Note that

  • The ==== doesn't have to be on a line of its own (strictly speaking, you didn't say it should be). If you want to only recognize it when it's the only thing on a line, use /^====$/ instead.
  • This prints the whole input if ==== is not seen. Checking that there is at least one needs an extra condition, see below.
  • Your sample output is missing the first empty line after the ====, but that might be just the post formatting. If you do want to remove it, add a pipe to sed 1d.

Another version that only starts storing input after the ==== separator is seen, effectively producing an empty output if the input doesn't contain the separator:

awk '/====/ { any = 1; t = ""; next } any { t = t $0 "\n"; } END { printf "%s", t; }' < sample.txt

Not the answer you're looking for? Browse other questions tagged or ask your own question.