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
  • 53,069
  • 19
  • 162
  • 212
ingank
  • 11
  • 2
  • What do you mean, "in any line is `foo_option=/bar/baz`? Does the file only contain that one line or else just one instance of it? – Nasir Riley Dec 01 '21 at 23:51
  • The config file contains many more lines. I fished out a line as an example. – ingank Dec 01 '21 at 23:59
  • Is that the only instance of that string that appears in the file? If not, then you need to provide more of the contents in the question. – Nasir Riley Dec 02 '21 at 00:01
  • Use a versioning system rather then comments for previous versions? That way you have a rollback history and log going back more then 1 revision, and know who has changed what when. – davidgo Dec 02 '21 at 01:42
  • @davidgo I used Git for code. But when it comes to server configs, that are rarely in repos, I do something similar to this where I comment out the default config line, add my own config line and might even add a comment like `2021-12-01: Changed setting to this.` – Giacomo1968 Dec 02 '21 at 03:49
  • Is there any reason you are not combining the commands like this: `sed -e 's/\(^foo_option .*$\)/#\1/' /path/to/conf.file && sed -e 's/^foo_option .*$/foo_option /new/opt/' /path/to/conf.file`. The reality is this all seems fine. There is no universal “best practice” for doing stuff like this but whatever works best for you. – Giacomo1968 Dec 02 '21 at 03:51
  • @giacomo1968 I imagine GIT is better, but for a long time I used fsvs which was basically subversion for configuration folders. – davidgo Dec 02 '21 at 07:23
  • Why not a versioning system? Because I want to use it for my documentation. I don't want to have to write: "and then turn up the `nano` and do this and that!" Its better for me to copy text and paste it in terminal... And that's why i'm asking for the 'best practice' (and maybe most readable) – ingank Dec 02 '21 at 18:49

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 $.

Kamil Maciorowski
  • 69,815
  • 22
  • 136
  • 202
  • Thank you very much for your analysis and the better explanation of pattern/hold space as i've found on the internet! What you definitely taught me: deal with `sed`, right now! :) – ingank Dec 02 '21 at 19:00