It can be easier if you use two sed
s. In fact, many things are, and they are often faster that way, as well, on multicore systems, at least.
: infile =;<<"" \
sed -e's/$/ /;s/hello/&\n\n/g' -e'# marks lines with " $" and splits matches' |
sed -e:n -e's/ $//;t' -eG -e'# sets up a test label, branches for " $"' \
-e's/o\n\{20\}$/o world/' -e'# stacks a byte per match, edits nth match' \
-e'x;N;x;N;s/\n\n*//;tn' -e'# completes the stacking; recycles to top' \
>outfile
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello world hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
(With a BSD sed
you'll want a literal newline in place of the n
for the \n
escapes in the right-hand substitution field)
It is usually easier to adapt the stream than to adapt the stream editor. The above sequence does just that: it marks each whole line in input with a trailing space, but otherwise splits output lines for each occurrence of hello
. The second sed
then needs only to look for a line which does not end in space to know that it should increment its stack count, and then only to explicitly match the 20th.
Of course it doesn't have to be that strict. You could drop the leading o
before \n\{20\}$
and leave it off the replacement. That would replace only from the 20th match through to the last in input. Or else you could do \n\{20,25\}
to handle only a range of matches. Or even: \n\{20,25\}\(\n\{15\}\)*$
to handle a range of 20,25 and every 10,15th occurrence thereafter.
Here's an output sample given the same input for that last mentioned...
hello hello hello hello hello hello hello hello hello
hello hello hello hello hello hello hello hello hello
hello hello world hello world hello world hello world hello world hello world hello hello
hello hello hello hello hello hello hello hello world hello world
hello world hello world hello world hello world hello hello hello hello hello
hello hello hello hello hello world hello world hello world hello world hello world
hello world hello hello hello hello hello hello hello hello
sed
or Perl for this; there's no general way to do this reliably in BSDsed
. Even for a fixed string it's hard; for a complex regex it's probably impossible. The following doesn't work:sed 's/^\(hello.*\)hello/\1world/'
Can you see why? – Wildcard Jan 13 '16 at 23:27-E
flag; i.e.sed -E
. – voices Jan 14 '16 at 00:35hello hello hello
then you could just do `s/hello hello/hello world/'. – Wildcard Jan 14 '16 at 02:45