Monday, 3 June 2013

Working on more than one line with sed's 'N' command

Yesterday I was asked to help solving a small sed problem. Considering that file (don't look too closely on the engineering of the defined elements):
<root>
  <key>key0</key>
  <string>value0</string>
  <key>key1</key>
  <string>value1</string>
  <key>key2</key>
  <string>value2</string>
</root>
The problem was: How to change value1 to VALUE!. The problem here is that you can't blindly execute a s command matching <string>.*</string>.
Sed maintains a buffer called the "pattern space" and processes commands on this buffer. From the GNU sed manual:
sed operates by performing the following cycle on each line of input: first, sed reads one line from the input stream, removes any trailing newline, and places it in the pattern space. Then commands are executed; each command can have an address associated to it: addresses are a kind of condition code, and a command is only executed if the condition is verified before the command is to be executed.

When the end of the script [(list of sed commands)] is reached, unless the -n option is in use, the contents of pattern space are printed out to the output stream, adding back the trailing newline if it was removed.3 Then the next cycle starts for the next input line.
So the idea is to first, use a /pattern/ address to select the the right <key> line, append the next line to the pattern space (with the N command) and finally run a s command on the buffer now containing both lines:
<key>key1</key>
  <string>value1</string>
And so we end up with:
$ cat input 
<root>
  <key>key0</key>
  <string>value0</string>
  <key>key1</key>
  <string>value1</string>
  <key>key2</key>
  <string>value2</string>
</root>
$ sed -e '/<key>key1<\/key>/{N;s#<string>.*<\/string>#<string>VALUE!<\/string#;}' < input 
<root>
  <key>key0</key>
  <string>value0</string>
  <key>key1</key>
  <string>VALUE!</string
  <key>key2</key>
  <string>value2</string>
</root>

No comments:

Post a Comment