-2

There is a command which gives me a long output. I want to just extract one line of it. This line changes, but the two lines before the desired line are always the same. How can I extract this line only by searching for the previous line(s): The command output example is like:

  foo-bar blahblah
  ===============
    foo bar foo bar foo bar.

  this string is always the same (search string)
  ===============
    the desired line is here

==========
foofoofoo
==========

I already tried

awk '/this string is always the same/{p=1} NF{out=$2}  END{if(p==1){print out}}'

and without the if part but it doesn't work

I would need the result to be like:

the desired line is here
ehsan khodadadi
  • 123
  • 1
  • 3
  • Are there any characters you *know* the desired line with start with or contain? Or just that it comes two lines after the string that's always the same? – Andy J Jun 23 '23 at 11:39
  • @AndyJ no, there is no known character. It always changes. – ehsan khodadadi Jun 23 '23 at 14:44
  • I don't now why my question is getting down-voted and nobody leaves a comment describing why are they down-voting it. When a question makes some discussions on answers, it means that it was a legit question. – ehsan khodadadi Jun 23 '23 at 14:49
  • 1
    I expect it's getting downvoted for not showing any attempt to solve the problem yourself. Since the forum exists to help people with their code, posting a question without code makes it a bad question. I wouldn't have answered if there wasn't already an awk answer posted that I didn't think was a good approach. – Ed Morton Jun 23 '23 at 17:53
  • @EdMorton thanks for your help. I will add my attempts to solve the issue myself. Do I have any other option to stop it from getting down-voted? – ehsan khodadadi Jun 23 '23 at 21:07
  • You're welcome. No, posting a question here without code is like asking your mechanic to diagnose your car trouble without showing them your car. You might get some guesses... – Ed Morton Jun 23 '23 at 23:35

4 Answers4

5

I wouldn't normally post an answer without the OP having posted their attempt to solve the problem themselves but since the already got answers including one using getline, here's another:

$ awk '/this string is always the same/{a[NR+2]} NR in a' file
    the desired line is here

That matches the current line against "this string..." and if found populates the array a[] with the current line number plus 2. Then it tests each line number and if it's in a[] prints that line.

Note that the above will work even if your input doesn't have 2 lines after this string is always the same:

$ printf 'foo\nthis\n' | awk '/foo/{a[NR+2]} NR in a'
$

or has 2 such lines back to back:

$ printf 'foo\nfoo\nthis\nthat\n' | awk '/foo/{a[NR+2]} NR in a'
this
that

See http://awk.freeshell.org/AllAboutGetline for why not to use getline for this.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • In your opinion, what's the main reason `getline` shouldn't be used for this kind of task? – Paolo Jun 23 '23 at 12:25
  • Oh, you're the author. I guess the answer is in there – Paolo Jun 23 '23 at 12:25
  • 1
    @Paolo I laid out all the reasons in that article I referenced but mainly it's bypassing awks implicit while-read-loop so if you ever had to search for any other pattern in any line or do anything else it'd require a complete rewrite and it'd quietly print the wrong line from the input if there weren't 2 lines after the line the regexp matched or getline failed. It's basically trying to treat awk like C and having to read each input line explicitly instead of just letting awk do what it does automatically and making your script **look** simple but actually being more complicated if done right. – Ed Morton Jun 23 '23 at 12:28
  • 1
    @EdMorton nice solution. – acrobat Jun 23 '23 at 13:09
  • Great solution, Thanks Ed. Could you please elaborate how it actually works? – ehsan khodadadi Jun 23 '23 at 13:19
  • I just added an explanation, let me know if you have any questions (after a glance at the awk man page and/or adding some prints to see if you can figure it out for yourself of course). – Ed Morton Jun 23 '23 at 14:00
3

I think using the -A option with grep will enable you to find the preceding line(s) along with the target line, which can then be further processed by sed to extract the desired line

something like this :

grep -A2 "this string is always the same" file.txt | sed -n '3p'

It is important to note that if the search string appears multiple times in the file, the command will extract the desired line for each occurrence. if you want to extract only the first occurrence, you can add the -m option to grep, which will stop searching after the first match is found

grep -A2 -m1 "this string is always the same" file.txt | sed -n '3p'

good luck!

Freeman
  • 9,464
  • 7
  • 35
  • 58
2

Using awk:

awk '/your_search_string_here/{getline;getline;print}' ./source_file

Using sed:

sed -n '/your_search_string_here/{n;n;p}' ./source_file

Both options will match your pattern & then skip two lines ahead before printing.

acrobat
  • 812
  • 4
  • 7
  • To write `getline;getline;print` to avoid surprises if the input isn't exactly as you expect, e.g. less than 2 lines after the `your_search_string_here` in some input file, would be `if ((getline line) > 0) if ((getline line) > 0) print line`. See http://awk.freeshell.org/AllAboutGetline. – Ed Morton Jun 23 '23 at 12:33
  • `awk '/your_search_string_here/ && getline && getline' ./source_file` – markp-fuso Jun 23 '23 at 13:04
  • @markp-fuso nice solution to that problem. Another problem with multiple getline calls is you can't easily handle the case of lines that match the pattern within the range before the line to print, e.g. if you had `foo\nfoo\nthis\nthat\n` in the input and wanted to print every 2nd line after `foo` the output should be `this` from the first `foo` and `that` from the 2nd one but that'd require adding duplicate tests on `foo` to also check the `getline` results. – Ed Morton Jun 23 '23 at 13:16
0

I would harness GNU AWK for this task following way, let file.txt content be

  foo-bar blahblah
  ===============
    foo bar foo bar foo bar.

  this string is always the same (search string)
  ===============
    the desired line is here

==========
foofoofoo
==========

then

awk '/this string is always the same/{k=3}--k==0' file.txt

gives output

    the desired line is here

Explanation: for each line I decrease k by 1 and if that value is equal to zero print it, when line matching regular expression is encountered I set k value to 3, it is then decrease to 2 in that line, to 1 in next and to 0 in subsequent. If this string is always the same (search string) is last line nothing will be printed, if this string is always the same (search string) last one matching is used for example for file.txt

this string is always the same (search string)
this string is always the same (search string)
this string is always the same (search string)
Able
Baker
Charlie

output is

Baker

(tested in GNU Awk 5.1.0)

Daweo
  • 31,313
  • 3
  • 12
  • 25
  • The reason to write `k && --k==0` or similar instead of just `--k==0` is that for a large enough input file `k` could wrap around and become 0 without the condition that set it ever having been true. See [printing-with-sed-or-awk-a-line-following-a-matching-pattern](https://stackoverflow.com/questions/17908555/printing-with-sed-or-awk-a-line-following-a-matching-pattern/17914105#17914105). – Ed Morton Jun 24 '23 at 11:12