102

I use this

cat foo.txt | sed '/bar/d'

to remove lines containing the string bar in the file.

I would like however to remove those lines and the line directly after it. Preferably in sed, awk or other tool that's available in MinGW32.

It's a kind of reverse of what I can get in grep with -A and -B to print matching lines as well as lines before/after the matched line.

Is there any easy way to achieve it?

1
  • 2
    Just for information: I'm analyzing logs in which entries are two-liners. So I want to find an entry matching the pattern and remove it as well as the next line. Hence I don't need to handle consecutive match lines, but thanks anyway for the completeness of your answers!
    – jakub.g
    Commented Nov 20, 2012 at 9:40

5 Answers 5

110

If you have GNU sed (so non-embedded Linux or Cygwin):

sed '/bar/,+1 d'

If you have bar on two consecutive lines, this will delete the second line without analyzing it. For example, if you have a 3-line file bar/bar/foo, the foo line will stay.

9
  • 1
    +1 for the length :) In my particular example I don't have consecutive bars so this one is super easy to remember.
    – jakub.g
    Commented Nov 20, 2012 at 9:30
  • 15
    sed '/bar/d' if you just want to "Remove line containing certain string" and not the next.
    – AJP
    Commented Apr 2, 2017 at 9:55
  • If I want to remove all the lines after math then?
    – Pandya
    Commented Feb 7, 2018 at 9:24
  • 2
    @Pandya That's different. You can use e.g. sed '/math/q' Commented Feb 7, 2018 at 12:44
  • 2
    @A.K. If you just want to delete the matching line, it's even simpler: sed '/bar/d' Commented Dec 21, 2018 at 7:36
19

If bar may occur on consecutive lines, you could do:

awk '/bar/{n=2}; n {n--; next}; 1' < infile > outfile

which can be adapted to delete more than 2 lines by changing the 2 above with the number of lines to delete including the matching one.

If not, it's easily done in sed with @MichaelRollins' solution or:

sed '/bar/,/^/d' < infile > outfile
2
  • The other plus in the AWK solution is that I can replace /bar/ with /bar|baz|whatever/. In sed that syntax doesn't seem to work.
    – jakub.g
    Commented Nov 21, 2012 at 9:58
  • 1
    @jakub.g , I have GNU sed (v4.4 now). Not sure about the others. What I know is that it uses "basic" regular expression syntax by default this is why your example didn't work. To achieve what you want you can either put a backslash in front of each vertical line or you can ask sed to use "extended" regular expressions. More information here: gnu.org/software/sed/manual/html_node/… . Please note that this is applicable to grep as well. Here's my own working example: echo $'0a\n1b\n2c' | sed '/0a\|1b/d'. Commented Jul 14, 2019 at 12:51
14

I am not fluent in sed, but it is easy to do so in awk:

awk '/bar/{getline;next} 1' foo.txt 

The awk script reads: for a line containing bar, get the next line (getline), then skip all subsequent processing (next). The 1 pattern at the end prints the remaining lines.

Update

As pointed out in the comment, the above solution did not work with consecutive bar. Here is a revised solution, which takes it into consideration:

awk '/bar/ {while (/bar/ && getline>0) ; next} 1' foo.txt 

We now keep reading to skip all the /bar/ lines.

1
  • 1
    To replicate grep -A 100%, you also need to handle any number of consecutive bar lines correctly (by removing the whole block and 1 line after).
    – jw013
    Commented Nov 19, 2012 at 17:07
9

You will want to make use of sed's scripting capabilities to accomplish this.

$ sed -e '/bar/ { 
 $!N
 d
 }' sample1.txt

Sample data:

$ cat sample1.txt 
foo
bar
biz
baz
buz

The "N" command appends the next line of input into the pattern space. This combined with the line from the pattern match (/bar/) will be the lines that you wish to delete. You can then delete normally with the "d" command.

2
  • How do I type a newline in console? Or this is script-only?
    – jakub.g
    Commented Nov 20, 2012 at 9:35
  • @jakub.g: with GNU sed: sed -e '/bar/{N;d}' sample1.txt
    – Cyrus
    Commented Jul 27, 2014 at 17:51
2
+50

If any line immediately following a match should be removed then your sed program will have to consider consecutive matches. In other words, if you remove a line following a match which also matches, then probably you should remove the line following that as well.

It is implemented simply enough - but you have to look-behind a little.

printf %s\\n     0 match 2 match match \
                 5 6 match match match \
                 10 11 12 match 14 15  |
sed -ne'x;/match/!{g;//!p;}'

0
6
11
12
15

It works by swapping hold and pattern spaces for each line read in - so the last line can be compared to the current each time. So when sed reads a line it exchanges the contents of its buffers - and the previous line is then the contents of its edit buffer, while the current line is put in hold space.

So sed checks the previous line for a match to match, and if its ! not found the two expressions in the { function } are run. sed will get the hold space by overwriting the pattern space - which means the current line is then in both the hold and pattern spaces - and then it will // check it for a match to its most recently compiled regular expression - match - and if it does not match it is printed.

This means a line is only printed if it does not match and the immediately previous line does not match. It also foregoes any unnecessary swaps for sequences of matches.

If you wanted a version that could drop an arbitrary number of lines occurring after a match it would need a little more work:

printf %s\\n    1 2 3 4 match  \
                match match 8  \
                9 10 11 12 13  \
                14 match match \
                17 18 19 20 21 |
sed -net -e'/match/{h;n;//h;//!H;G;s/\n/&/5;D;}' -ep

...replace the 5 with the number of lines (including the matched line) that you would like to remove...


1
2
3
4
12
13
14
21

You must log in to answer this question.

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