0

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)

mYself
  • 191
  • 1
  • 1
  • 11
  • 1
    Does it have to be `sed` and a single command? Can you give an example input and output for testing? But first thought would - if you're doing keyed on the same pattern, doesn't that mean you're deleting then skipping? – Sobrique Sep 23 '15 at 20:44
  • Yes, it has to be a one line command, preferably sed. If sed isn't an option then maybe others will do (I haven't looked for an answer outside of sed). I will update the initial post with an example. – mYself Sep 23 '15 at 20:50
  • Why is my question being downvoted? – mYself Sep 23 '15 at 21:12
  • There was an answer before suggesting to use `x;` after `{`. This partially works as it deletes everything from the line with the matching pattern while leaving a blank line. Is there a way to remove the whole line? – mYself Sep 23 '15 at 21:28
  • Please tell me what's wrong with my question so I can fix it. I'm always trying to properly formulate my questions before posting. – mYself Sep 23 '15 at 22:06
  • Any ideas how can I do this with `sed`? The answers below are too complex for my purpose. – mYself Sep 23 '15 at 22:40

4 Answers4

2

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'
Community
  • 1
  • 1
karakfa
  • 66,216
  • 7
  • 41
  • 56
  • How can I use `($(ose),)` as the pattern? – mYself Sep 23 '15 at 21:47
  • Is it literal "($(ose),)"? Use the quoted value as a variable and change `/patttern/` to $0~var – karakfa Sep 23 '15 at 21:56
  • Yes, it's literal as you can see in the example. Using a variable requires an extra line so it defeats the purpose. Anyway, I wasn't able to get it working. – mYself Sep 23 '15 at 22:26
  • setting the var is for convenience and can be done with awk `-v` option, if you like you can embed the pattern by escaping `$ ( )` as in `/\(\$\(ose\),\)/` – karakfa Sep 23 '15 at 22:32
1

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).

Sobrique
  • 52,974
  • 7
  • 60
  • 101
1

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 }.
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • This seems to work but how can I save the results to a file? Adding `-i` doesn't work. – mYself Sep 23 '15 at 21:51
  • The standard way to do that is to redirect to a temporary file then overwrite the original: `awk '...' file > tmp && mv tmp file`. – Tom Fenech Sep 23 '15 at 21:54
  • I already tried redirecting the output to a different file but awk hangs when I do that (i.e. waits for input). – mYself Sep 23 '15 at 21:58
  • Sounds like a syntax error to me. Using the code in my answer and assuming that the file is called `file`, it would be: `awk '/ifeq/ { n = 6; next } n && !--n { c = 1 } c && c++ < 6 { next }1' file > tmp && mv tmp file`. – Tom Fenech Sep 23 '15 at 22:00
  • This worked, however, the resulting file is not correct. Several lines are missing across the file. – mYself Sep 23 '15 at 22:12
  • You probably need to change `/ifeq/` to be more specific (it was just an example of a pattern which works for the input you showed). If the string you're trying to match is `($(ose),)`, then you could use `/\(\$\(ose\),\)/` as your pattern (i.e. escape the parentheses and $). – Tom Fenech Sep 23 '15 at 22:16
1

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".

Cyrus
  • 84,225
  • 14
  • 89
  • 153
  • Now shoot me. Such an simple and clever workaround. Why didn't I think of this before? THANK YOU countless times!! – mYself Sep 23 '15 at 22:59