-1

If a file contains the following:

Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7
Line 5
Line 8
Line 9

How to delete the lines below two lines after "Line 5", so that the file will now contain only the following?

Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7

Note: This is a slightly improved and reversed version of the question Delete all lines before n lines above the line containing the first match

5
  • Can you not apply one of the previous solutions given to you? It should be easy enough to see what's going on there. If not, have you tried so far? Commented Dec 16, 2023 at 10:55
  • @ChrisDavies No, it isn't as easy for me as it might be for you due to our different levels of expertise. I could only apply the sed solution (tac ip.txt | sed '/Line 5/{n; n; q}' | tac) but I prefer something else that can be used in a shell script using a variable etc., ideally something like the exec.awk solution there.
    – Sadi
    Commented Dec 16, 2023 at 14:11
  • 3
    Sadi, that would be really useful discussion to add to your question. Showing what you've tried can help people understand what precisely you're struggling with, and this can help them form an answer that is most useful to you Commented Dec 16, 2023 at 17:14
  • @ChrisDavies There isn't much to add really. With my elementary level it's impossible to properly understand all these complex (e.g. awk) formulations well enough to adapt them to this question. And the only simple one I can handle (tac file | '/Line 5/{n; n; q}' | tac) was not quite what I wanted, because it didn't stop at the first match, which wouldn't work if there were several instances of the match pattern.
    – Sadi
    Commented Dec 17, 2023 at 10:17
  • 1
    Sadi, if you add your own attempt(s) to the question people can use those as a starting point to help you get an answer you understand Commented Dec 17, 2023 at 14:17

5 Answers 5

3

With sed:

$ sed '/^Line 5$/{n;n;q;}' file
Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7

To edit the file in place, add the -i option for GNU sed or compatible or -i '' for FreeBSD sed or compatible.

2

Using any awk:

$ awk -v n=2 'c&&!--c{exit} $0 == "Line 5"{c=n+1} 1' file
Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7

See printing-with-sed-or-awk-a-line-following-a-matching-pattern/17914105#17914105 for an explanation and related idioms.

1
  • 1
    In the end, I've managed to create a shell script that starts with variables matchregex="^Line 5$" and keeplines=2 followed by the awk command awk -v mr="$matchregex" -v n="$lines" 'c&&!--c{exit} $0 ~ mr {c=n+1} 1' file
    – Sadi
    Commented Dec 19, 2023 at 14:55
2

Here are a couple of ways, both of which assume you want to start deleting two lines after a line that contains nothing but the exact string Line 5:

  • perl

    $ perl -lne '$k=$. if /^Line 5$/; print; exit if $k and $.==$k+2;' file
    Line 1
    FooLine 5
    Line 2
    Line 55
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    

    You can use perl -i -lne '$k=$. if /^Line 5$/; print; exit if $k and $.==$k+2;' file to edit the file in place.

  • awk

    awk '(/^Line 5$/){ k=NR } 1; (k && NR==k+2){exit};' file
    Line 1
    FooLine 5
    Line 2
    Line 55
    Line 3
    Line 4
    Line 5
    Line 6
    Line 7
    
2

Using awk:

$ awk -v var=2 '1;/^Line 5$/{c=NR+var}NR==c{exit}'

Using 2 pass way

$ awk -v var=2 
    '
    pass==1 && ($0 == "Line 5"){c=FNR+var; nextfile} 
    pass==2 && FNR<=c
    '
 pass=1 file pass=2 file

Or using ed slightly changed:

$ printf '%s\n' '1,/^Line 5$/2p'  | ed -s file
4
  • 1
    The first awk command seems very useful for shell scripting with variables for the match regex and the number of lines to be excluded: awk -v var=$lines "NR==c{print; exit}; /$matchregex/{c=NR+var}1" file
    – Sadi
    Commented Dec 17, 2023 at 11:01
  • 1
    @Sadi do not let shell variables expand to become part of a shell script, assign their value to an awk variable instead, i,e, do awk -v mr="$matchregex" '...$0 ~ mr ...', not awk ".../$matchregex/...", see how-do-i-use-shell-variables-in-an-awk-script. Also make sure to quote shell variables when you use it, i.e. do var="$lines", not var=$lines, and use single quotes around all strings (including scripts) until if/when you NEED double quotes, see mywiki.wooledge.org/Quotes.
    – Ed Morton
    Commented Dec 17, 2023 at 12:53
  • @EdMorton Thank you so much! I'm just beginning to learn awk from scratch. It seems in a shell script I can first define a couple of variables, and then use them in an awk command starting with -v but I have no idea why this doesn't work even if I use just one variable like this: awk -v var="^Line 5$" '1;/var/{c=NR+2}NR==c{exit}'
    – Sadi
    Commented Dec 19, 2023 at 8:48
  • 1
    You're welcome. Please read the article I referenced as it explains that - how-do-i-use-shell-variables-in-an-awk-script.
    – Ed Morton
    Commented Dec 19, 2023 at 10:51
0

Using Raku (formerly known as Perl_6)

~$ raku -pe 'if m/^Line \h 5$/ {.put; get.put xx 2; last};'  file

OR:

~$ raku -pe 'm/^Line \h 5$/ && .put && get.put xx 2 && last;'  file 

OR:

~$ raku -ne 'm/^Line \h 5$/ ?? (.put; get.put xx 2; last) !! .put;'  file

Here are answers coded in Raku, a member of the Perl-family of programming languages. Raku features high-level support for Unicode, built-in.

In the first pair of answers, the -pe autoprinting linewise flags are used. Lines will autoprint until the desired line is read, as determined via Regex. At that point the desired line is output, the code instructs to get two more lines and output them, then that's the last (i.e. the code exits). Note, for the second of these two examples andthen can be used instead of &&.

In the third answer, the -ne non-autoprinting linewise flags are used. Each line is read into Raku's Test ?? True !! False ternary operator to find the desired line via Regex. If the desired line is True, it is output, the code instructs to get two more lines and output them, then that's the last (i.e. the code exits). If the desired line via Regex is False, it is simply output and the linewise reading continues, potentially to the end of the file.

Sample Input:

Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7
Line 5
Line 8
Line 9

Sample Output:

Line 1
FooLine 5
Line 2
Line 55
Line 3
Line 4
Line 5
Line 6
Line 7

NOTE: With all three answers above, blank lines are handled fine, but the code will throw an error if you try to get.put return lines that don't exist (e.g. if you're too close to the end of the file).

To handle this more gracefully (return whatever lines are available after a matching line, up to the requested number), use the following code which is similar to @terdon's Perl answer. Below set $k to equal "n+1" lines to return the current line plus "n"-lines that follow ($k=3 gives same output as above):

~$ raku -ne 'my $k=3 if m/^Line \h 5$/; $k.put && --$k; last unless $k;'  file

https://docs.raku.org
https://raku.org

1
  • @terdon my Raku interpretation ;-). Commented Dec 21, 2023 at 23:20

You must log in to answer this question.

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