mYself
mYself

Reputation: 301

SED: delete lines after pattern match while skipping a few lines, then delete the pattern line

Let's say I want to delete some lines after the pattern while skipping some lines in-between, and then delete the line with the pattern itself. I can do this using the following two commands:

sed -i '/PATTERN/{n;n;n;n;n;n;N;N;N;N;d}' file  # match pattern, skip 5 lines, then delete 5 consecutive lines
sed -i /PATTERN/d file  # delete line with pattern

But I want to do this in a single command. The problem here is obvious as the pattern change affects both expressions so the change must be specified in a single expression. Is there a way to achieve this?

UPDATE:

Example input:

...
ifeq ($(ose),)
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
else
    dh_installdocs \
        $(archdir)/UserManual*.pdf
    rm $(addprefix $(archdir)/,UserManual*.pdf)
endif
...

Example output:

...
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
...

UPDATE 2:

The example file can be obtained here: http://download.virtualbox.org/virtualbox/5.0.4/VirtualBox-5.0.4.tar.bz2 (debian/rules)

Upvotes: 0

Views: 337

Answers (4)

Cyrus
Cyrus

Reputation: 88563

Another approach:

sed '/($(ose),)/,/^endif/{/($(ose),)/d;/^else/,/^endif/d}' file

Output:

...
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
...

Add option -i to edit "in place".

Upvotes: 1

karakfa
karakfa

Reputation: 67467

awk to the rescue!

awk 's&&s--{print;next} d&&d--{next} /pattern/{d=5;s=5;next} 1'

set s for skip and d for delete, using Ed Morton's smart counters

For your pattern you need to escape special chars

awk 's&&s--{print;next} d&&d--{next} /\(\$\(ose\),\)/{d=5;s=5;next} 1'

Upvotes: 2

Sobrique
Sobrique

Reputation: 53478

If it doesn't have to be sed I would offer that perl can work in "sed mode" and use regular expressions along with some more complex scripting logic.

E.g. a for loop:

#!/usr/bin/env perl
use strict;
use warnings;

while ( <DATA> ) {
   if ( m/ifeq\ \(\$\(ose\)\,\)/ ) {

       print "". <DATA> for 1..5;
       <DATA> for ( 1..5 );
   }
   else { print };
}

__DATA__
...
ifeq ($(ose),)
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
else
    dh_installdocs \
        $(archdir)/UserManual*.pdf
    rm $(addprefix $(archdir)/,UserManual*.pdf)
endif
...

Which you could do 'inline' like sed:

perl -i -ne 'if ( m/ifeq\ \(\$\(ose\)\,\)/ ) { print "". <> for 1..5; <> for ( 1..5 );} else {print}'

Alternatively, you can use perl's "range operator" to detect if you're between two patterns. That depends rather more on the rest of your file though (I assume 'else' ... 'endif' isn't exactly uncommon).

Upvotes: 1

Tom Fenech
Tom Fenech

Reputation: 74596

Here's a way you could do it using awk:

awk '/ifeq/ { n = 6; next } n && !--n { c = 1 } c && c++ < 6 { next }1' file

It's a bit tricky looking but the logic is as follows:

  • When the pattern /ifeq/ matches, set the variable n to 6 and skip the line
  • n && !--n is only true when n is 1 (it is written this way so that n is only decremented until it reaches 0)
  • When c has been set, skip each line until c reaches 6
  • 1 at the end means that any line that has not been skipped is printed (the default action is
    { print }.

Upvotes: 1

Related Questions