Sign up ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

I have the following code that will remove lines with the pattern banana and 2 lines after it:

sed '/banana/I,+2 d' file

So far, so good! But I need it to remove 2 lines before banana, but I can't get it with a minus sign or whatever (similar to what grep -v -B2 banana file should do but doesn't):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'
share|improve this question
The easiest is to load all the data into an array, skip the undesired lines then output what remains: awk '{l[m=NR]=$0}/banana/{for(i=NR-2;i<=NR;i++)delete l[i]}END{for(i=1;i<=m;i++)if(i in l)print l[i]}'. This is not efficient, so this is just a hint, not a solution. – manatwork Jan 24 '12 at 16:38
Just do tac file | sed ... | tac. :P – angus Jan 24 '12 at 16:54
Thanks, @manatwork, it works! – Teresa e Junior Jan 24 '12 at 16:55
@angus I didn't think about it ;) – Teresa e Junior Jan 24 '12 at 17:00
you could have done sed '/banana/,+2d' file that will also work – Akaks Jan 6 at 12:32

4 Answers 4

up vote 7 down vote accepted

Sed doesn't backtrack: once it's processed a line, it's done. So “find a line and print the previous N lines” isn't going to work as is, unlike “find a line and print the next N lines” which is easy to graft on.

If the file isn't too long, since you seem to be ok with GNU extensions, you can use tac to reverse the lines of the file.

tac | sed '/banana/I,+2 d' | tac

Another angle of attack is to maintain a sliding window in a tool like awk. Adapting from Is there any alternative to grep's -A -B -C switches (to print few lines before and after )? (warning: untested):

{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to omit.
match($0, pattern) {   # the current line matches
    for (i in h) {
        delete h[i];   # delete before and after lines
    until=NR+after;    # record the last after line to print
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    print h[NR-before];         # print line too old to be a before line
    delete h[NR-before];        # remove line too old to be a before line
END {exit !until}               # exit status: 0 if there was a match, else 1
share|improve this answer
sed can do sliding windows too, but the resulting script is typically so unreadable that it's easier to just use awk. – jw013 Jan 24 '12 at 20:30
@Gilles.. The awk script is not quite right; as-is it prints blank lines and misses the last lines. This seems to fix it, but it may not be ideal or right itself: if (NR-before in h) { print...; delete...; } ... and in the END section: for (i in h) print h[i] ... Also, the awk script prints the matching line, but the tac/sec version does not; but the question is a bit ambiguous on this.. The "original" awk script, to which you provided a link, works fine.. I like it... I'm not sure how the above 'mod' affects the print after lines... – Peter.O Jan 25 '12 at 14:56

This is pretty easy with ex or vim -e

    vim -e - $file <<@@@

The expression reads: for every line containing banana in the range from the current line -2 to the current line, delete.

What's cool is that the range can also contain backwards and forwards searches, for example this will delete all sections of the file starting with a line containing apple and ending with a line containing orange and containing a line with banana:

    vim -e - $file <<@@@
share|improve this answer

Using the "sliding window" in perl:

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'
share|improve this answer
+1 This is the only answer that actually works with overlapping ranges (I haven't tried vim but it's derived from ed so I'll just assume it equally fails). – don_crissti Jul 17 at 20:24

Using man 1 ed:


# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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