1

I want to edit some config files on in the CLI without a wysiwyg editor but instead using sed.

Is there a best practice for doing that?

Here are a few examples of what I mean:

Config file example 1

A line in the config file could be this:

foo_option /bar/baz

And after editing it should look like this:

#foo_option /bar/baz
foo_option /new/opt

Config file example 2

A line in the config file could be this:

foo_option=/bar/baz

And after editing it should look like this:

#foo_option=/bar/baz
foo_option=/new/opt

I'm think that the stream editor sed is the tool of choice, I've already experimented with it:

sed -e 's/\(^foo_option .*$\)/#\1/' /path/to/conf.file

And:

sed -e 's/^foo_option .*$/foo_option /new/opt/' /path/to/conf.file

But how can I make both changes (two lines, one commented out and one with the new content) at the same time?

And would it really be the best practice with sed at all?

Giacomo1968
  • 58,727
ingank
  • 11
  • 2

1 Answers1

1

I don't know if it's "the best practice", but sed code for this is relatively simple:

sed '/^foo_option / {s/^/#/; a\
foo_option /new/opt
}' /path/to/conf.file

It solves the example 1. To solve the example 2 you need to replace each occurrence of foo_option (there are two occurrences, and note the trailing space) with foo_option= in the sed code.

Universal code that solves both examples is more complicated because what you append depends on the presence of = in the old line; therefore you cannot use a with a fixed string. Try this:

sed '/^foo_option[= ]/ {
  h; s/^/#/; p
  g; s|\([= ]\).*|\1/new/opt|
}' /path/to/conf.file

The above code uses the original line twice. First it copies the line from the pattern space to the hold space (h), adds # in front and prints the pattern space (p). Next it copies back from the hold space (g) and places /new/opt in the right place; this time the pattern space gets printed because sed (without -n) prints by default when it's done processing the line (no p needed).

Notes:

  • I used | in the second s, not /, because /new/opt contains /. Your attempt with s/^foo_option .*$/foo_option /new/opt/ was invalid, too many / characters. In general you can use almost any character.

  • In your tries you used ^foo_option inside the pattern of s to match the right line; then you needed it in the replacement (as \1 or literal foo_option ). A better way is to address the line by using a regex match, like I did: /^foo_option / or so. Then the following command (or { } block) does not need to check if it's the right line, you know it is. You can use commands other than s; and s often can be simplified greatly. Even in a simple case of adding # I prefer /^foo_option / s/^/#/ to s/\(^foo_option \)/#\1/. The former code is more readable and it's the Right Way.

  • In regular expressions * is greedy, so .*$ (you used it) does not really need $.