Ajay
Ajay

Reputation: 133

Replace content of previous line after finding a match using sed

I am trying to replace content in the previous line of a search.

My file:

<RECORD>
<TOKEN data = "670"/>
<ID data ="10647043"/>
<NAME data="m11111"/>

Here if I search for m11111, then I need to go to the previous line and replace 10647043 with a different value. Sed that I tried:

sed '/m11111/{g;/=/s/=.*/="9283"\/>/g;};h' test.txt

Is there a way with sed? If not with sed, any other way to do it ?

Thanks Ajay

Upvotes: 8

Views: 7511

Answers (2)

Jedi
Jedi

Reputation: 3348

This is probably a slower solution than @John1024's but is a sed-only solution that does not read the entire file into memory (it needs two passes though).

sed -i '$!N;/\n.*m11111/s/"\([^"]*\)"/"9243"/' filename
sed -i '1n; $!N;/\n.*m11111/s/"\([^"]*\)"/"9243"/' filename

As @John1024 pointed out, the first sed replaces the previous content in quotes in-place if m11111 appears on an even-numbered line; the second one repeats the process, but ignores the first line to cover all odd-numbered lines.

Upvotes: 0

John1024
John1024

Reputation: 113834

GNU sed

Assuming that test.txt is not gigabytes in size and that you have GNU sed (gsed on a Mac), try:

$ sed -zE 's/10647043([^\n]*\n[^\n]*m11111)/9283\1/' test.txt
<RECORD>
<TOKEN data = "670"/>
<ID data ="9283"/>
<NAME data="m11111"/>

How it works

  • -z

    This tells sed to read the whole file at once. Technically, it reads until NUL characters but, since no sensible text file has NUL characters, this is in practice the same as reading the whole file.

  • -E

    This tells sed to use extended regular expressions so that we don't have to type so many backslashes.

  • s/10647043([^\n]*\n[^\n]*m11111)/9283\1/

    This looks for 10647043 followed any any character except a newline, followed by a newline, followed any characters except a newline, follwed by m11111. This replace the 10647043 with 9283, keeping everything else the same.

BSD (OSX) sed

sed -E 'H;1h;$!d;x; s/10647043([^\n]*\n[^\n]*m11111)/9283\1/' test.txt

The change here is the use of H;1h;$!d;x to read the whole file in at once.

Using awk

This reads in just one line at a time. If the current line contains m11111, then the previous line (stored in the variable last) is modified.

$ awk '/m11111/{sub(/10647043/, "9283", last)} NR>1{print last} {last=$0} END {print last}' test.txt
<RECORD>
<TOKEN data = "670"/>
<ID data ="9283"/>
<NAME data="m11111"/>

Upvotes: 15

Related Questions