10

Consider:

echo -e "a\nb\nc\nd\ne\nf\ng\nh" | sed '3,5a test'

This will match lines 3, 4 and 5.

But I am trying to match only lines 3 and 5 (not 4). And append 'test' after them.

Peter Mortensen
  • 1,029
  • 1
  • 8
  • 10
Aquarius Power
  • 4,099
  • 5
  • 38
  • 56

3 Answers3

9
echo ... | sed -e '3a test' -e '5a test'

If the operation is more complex than in this case then you can use a structure like this:

sed 'b pattern; : action; a \
lalala
b end; : pattern; 3b action; 5b action; : end'

I.e. you put all commands you need between b pattern; and b end;.

And you add all your patterns (line numbers or whatever) after : pattern;.

What happens is this:

  1. The first command jumps over the action part (maybe it's easier to read if the patterns are at the beginning and b end; directly before the action part).

  2. If a pattern matches then the execution jumps to the action part. After the action part execution jumps to the end.

I try to tidy this up:

sed '3b action; 5b action; b end; : action; a \
lalala
: end'

In a single line would be like:

sed "3b idAction; 5b idAction; b; : idAction; a test"

Portably, you need to write it:

sed '
   3b action
   5b action
   b
   : action
   a\
   lalala'

(b without label branches to the end, so you don't need an explicit end label, ; is valid a character in a label in standard sed implementations).

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
Hauke Laging
  • 88,146
  • 18
  • 125
  • 174
5

With sed (see @HaukeLaging's answer)

With awk:

$ echo -e "a\nb\nc\nd\ne\nf\ng\nh" | awk 'NR==3 || NR==5{$0=$0"\ntest"}1;'

With perl:

$ echo -e "a\nb\nc\nd\ne\nf\ng\nh" | perl -pe '$_ .="test\n" if $. == 3 || $. == 5'
terdon
  • 234,489
  • 66
  • 447
  • 667
  • Can golf your perl a bit, using the "smart match" operator: `perl -lnE 'say if $. ~~ @{[3,5]}'` – glenn jackman Feb 28 '14 at 17:28
  • @glennjackman ah, yes indeed, thanks. I have to admit that I'm a bit wary of `~~`, don't understand it as well as I should. Also, both `say` and `~~` need perl v >= 5.10 right? – terdon Feb 28 '14 at 17:29
  • I need to append 'test' after that lines, I see now it would have been better to have clarified that from the beginning. I just edited the OP. – Aquarius Power Feb 28 '14 at 17:32
  • @AquariusPower, `perl -pE 'say "test" if $. ~~ @{[4,6]}'` -- this *prepends* the line: perl's `-p` does an implicit print at the *end* of the given program. – glenn jackman Feb 28 '14 at 17:36
  • @AquariusPower also see updated answer. – terdon Feb 28 '14 at 17:38
  • @glennjackman it doesn't always though, at least it seems to ignore `next`. I've never been able to figure out quite how it works. For example, try `echo -e "a\nb\nc" | perl -pe 'next if /b/'`. That will still print the second line despite the `next`. – terdon Feb 28 '14 at 17:39
  • That's because the print is in a "continue" block: `while (<>) { your code ...} continue {print}`, and only "redo" and "last" skip the continue block. See "[perldoc perlrun](http://perldoc.perl.org/perlrun.html)" and "[perldoc perlsyn](http://perldoc.perl.org/perlsyn.html)". – glenn jackman Feb 28 '14 at 17:44
  • @glennjackman yes, I was actually just typing a question on this so I re-read `man perlrun` and saw that I had missed that. Thanks :) – terdon Feb 28 '14 at 17:45
5

As @HaukeLaging aleady said, this command does what you want:

sed -e'3a test' -e'5a test'

Now, this can become rather cumbersome to type if you want to match, e.g., 20 lines.

In these cases, assuming your shell supports brace expansion, you can use this command instead:

sed -e{3,5}'a test'

(Note that the braces and the comma must remain unquoted.)

As a result, the shell will pass the arguments to -e3a test and -e5a test to sed, which is exactly what the first command does.

Dennis
  • 148
  • 7