Village
Village

Reputation: 24373

How to find and replace every match except the first using sed?

I am using sed to find and replace text, e.g.:

set -i 's/a/b/g' ./file.txt

This replaces every instance of a with b in the file. I need to add an exception, such that sed replaces every instance of a with b, except for the first appearance in the file, e.g.:

There lived a bird who liked to eat fish.
One day he fly to a tree.

This becomes:

There lived a bird who liked to ebt fish.
One dby he fly to b tree.

How can I modify my sed script to only replace every instance of a with b, except for the first occurrence?

I have GNU sed version 4.2.1.

Upvotes: 4

Views: 6545

Answers (3)

Thor
Thor

Reputation: 47089

One way is to replace all and then reverse the first replacement (thanks potong):

sed -e 'y/a/\n/' -e 's/\n/a/g' -e 'y/\n/b/'

Newline serves as an intermediate so strings beginning with b work correctly.

The above works line-wise, if you want to apply it to the whole file, first make the whole file into one line:

<infile tr '\n' '^A' | sed 'y/a/\n/; s/\n/a/; y/\n/b/' | tr '^A' '\n'

Or more briefly using the sed command from potong's answer:

<infile tr '\n' '^A' | sed 's/a/b/2g' | tr '^A' '\n'

Note ^A (ASCII 0x01) can be produced with Ctrl-vCtrl-a. ^A in tr can be replaced by \001.

This assumes that the file contains no ^A.

Upvotes: 2

You can do a more complete implementation with a script that's more complex:

#!/bin/sed -nf

/a/ {
    /a.*a/ {
        h
        s/a.*/a/
        x
        s/a/\n/
        s/^[^\n]*\n//
        s/a/b/g
        H
        g
        s/\n//
    }

    : loop
    p
    n
    s/a/b/g
    $! b loop
}

The functionality of this is easily explained in pseudo-code

if line contains "a"
    if line contains two "a"s
        tmp = line
        remove everything after the first a in line
        swap tmp and line
        replace the first a with "\n"
        remove everything up to "\n"
        replace all "a"s with "b"s
        tmp = tmp + "\n" + line
        line = tmp
        remove first "\n" from line
    end-if

    loop
        print line
        read next line
        replace all "a"s with "b"s
        repeat loop if we haven't read the last line yet
    end-loop
end-if

Upvotes: 5

potong
potong

Reputation: 58351

This might work for you (GNU sed):

sed 's/a/b/2g' file

or

sed ':a;s/\(a[^a]*\)a/\1b/;ta' file

This can be taylored e.g.

sed ':a;s/\(\(a[^a]*\)\{5\}\)a/\1b/;ta' file

will start replacing a with b after 5 a's

Upvotes: 5

Related Questions