chesschi chesschi - 6 months ago 50
Ini Question

sed: How to add a key/value pair at the end of the section in INI file

In order to add a key/value pair at the end of the section in INI file, I would like to:


  1. Find the match section name and check if next square bracket (i.e. ^[) is available. If so, find the last occurrence of key/value pair within these lines (i.e. from match section name to next square bracket). Insert the key/value pair at the end of last occurrence of key/value pair.

  2. Append the key/value pair to the end of the file if next square bracket is not available.



For case (1), I can use sed to check if next square bracket is available, i.e.

sed -i -e "/^\[$section\]/,/^\[.*\]/ ..."


However, I have no idea how to match the last occurrence of key=value. The reason of matching the last occurrence (instead of adding ke/value pair just before next square bracket) is because I can choose to introduce newline or not before the new key/value pair in the INI file. For example, if I want to add key10=value10

[section1]
...
key8=value8
key9=value9
\n (could be multiple newline)
[section2]


It becomes

[section1]
...
key8=value8
key9=value9
\n (optional)
key10=value10
\n (could be multiple newline)
[section2]


Please could you kindly suggest how can I achieve this?

Answer Source

For the sake of completeness (and because sed hackery is mindbendy fun), you can do it with sed like this:

/^\[section1\]/,/^\[/ {
  x
  /^$/ !{ x; H }
  /^$/  { x; h }
  d
}
x
/^\[section1\]/ {
  s/\(\n\+[^\n]*\)$/\nkey10=value10\1/
  p
  x
  p
  x
  d
}
x
p

Put that in a file, say foo.sed, and run sed -n -f foo.sed foo.ini. Or, if you prefer,

sed '/^\[section1\]/,/^\[/ { x; /^$/ !{ x; H }; /^$/ { x; h; }; d; }; x; /^\[section1\]/ { s/\(\n\+[^\n]*\)$/\nkey10=value10\1/; p; x; p; x; d }; x' foo.ini

...as if it wasn't hard enough to read already.

This is fun with the hold buffer; the first bit matches all lines from [section1] to the beginning of the next section and puts then all in the hold buffer (and breaks out there). If we're not in that section, the rest swaps in the hold buffer, checks if section1 is in it, if so substitutes key10=value10 into it at the appropriate position, prints it, swaps the pattern space back, prints it, swaps the hold space back and deletes it (now the line after section1 is in the hold buffer, but we don't care about it anymore). The rest of the time, the pattern space is just swapped back and printed, so from there it works like cat.

EDIT: NeronLeVelu pointed out that this only works if there is a section after [section1] that the pattern range can match. Where this is not guaranteed, special handling for the last line is needed in case section1 hasn't been handled by then. This can do it:

$ {
  H
  x
  /^\[section1\]/ {
    s/\n\+$//
    a key10=value10
    x
  }
  x
  p
  d
}
/^\[section1\]/,/^\[/ {
  x
  /^$/ !{ x; H }
  /^$/  { x; h }
  d
}
x
/^\[section1\]/ {
  s/\(\n\+[^\n]*\)$/\nkey10=value10\1/
  p
  x
  p
  x
  d
}
x
p

This will handle the last line differently from the rest; it'll append it to the hold buffer, check if section1 still needs handling, if so append key10=value10 and swap back, then swap back again and print what is then the pattern space. This confusing bit of shunting means that if section1 is still in the hold buffer, then the hold buffer is printed, and otherwise the last line as it came in.

By this point, I feel compelled to point out that once a sed script has become this long, it is worth considering other options.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download