1

Say I have a config file:

[main]
foo = bar
[option]
opt1 = opt2
opt3 = opt4
[extra]
[footer]
[tail]
print = true
[end]

I want to print the headers ([text]) only if there are options underneath. So the output should be:

[main]
foo = bar
[option]
opt1 = opt2
opt3 = opt4
[tail]
print = true
  • Why must it be sed, rather than awk or perl? – Ed Grimm Feb 20 at 4:26
  • @EdGrimm - It doesn't really. I just wanted some simple one-liner. I've removed sed from the question. – DarkHeart Feb 20 at 4:35
1

Normally, I'd write this in multiple lines for legibility, but since you asked for a one-liner:

perl -ne '$head = $_ and next if /^\[/; $head and print $head and undef $head; print'
  • 1
    I recall seeing awk that could do it in fewer characters, but I don't use awk due to the overhead from always splitting lines into words. For most uses, that difference in time is not relevant, but I've spent too much of my career writing log parsing scripts that consume multi-gigabyte logs, so I haven't exercised my awk muscles. – Ed Grimm Feb 20 at 4:44
  • 2
    Thanks - I can convert that to awk as awk '/^\[/{head=$0; next}head{print head;head=""}1' – DarkHeart Feb 20 at 4:56
1

portable sed; must not gnu sed, let your file is conf

 sed -E 'N;/^\[.+\]\n\[.+\]$/!P;D' conf

if gnu sed set it to basic portable

sed --posix -E 'N;/^\[.+\]\n\[.+\]$/!P;D' conf
  • POSIX sed has no -E option, but ok, "more portable". – Kusalananda Apr 7 at 9:17
0
$ awk '/^\[/ { head = $0; next } head != "" { print head; head = "" } { print }' file
[main]
foo = bar
[option]
opt1 = opt2
opt3 = opt4
[tail]
print = true

This awk program saves each header found into the variable head and immediately skips to the next line of input.

If the line is not a header line, and if the head variable contains something, the header is outputted. The current line is then outputted.

This is a more or less straight translation of Ed Grimm's answer into awk.


A more or less direct translation into sed, where the hold space is used to keep the most recent header, would look like

/^\[/ {
    h;    # store header in hold space (overwriting)
    d;    # delete pattern space, start next cycle
}

x;                   # swap with hold space
/./ { p; s///g; };   # if non-empty, print and empty
x;                   # swap with hold space

Or, as a one-liner,

$ sed -e '/^\[/{ h;d; }' -e 'x; /./{ p;s///g; }' -e x file
[main]
foo = bar
[option]
opt1 = opt2
opt3 = opt4
[tail]
print = true

An alternative but shorter sed variation that relies on the fact that there's an empty section/header at the end of the file:

$ sed -n -e'/^\[/{ x;/\n/p;d; }' -e H file
[main]
foo = bar
[option]
opt1 = opt2
opt3 = opt4
[tail]
print = true

This stores the header and the other lines associated with it in the hold space. When a new header is found, the hold space is swapped in and a check for a newline character is done on it. If one is found, it is printed. Other lines are simply appended to the hold space.

As awk, this would look like

awk '/^\[/ { if (sect ~ ORS) print sect; sect = $0; next } { sect = sect ORS $0 }' file
  • This looks familiar ... hmmm – DarkHeart Apr 7 at 7:09
  • @DarkHeart Yes, it's more or less what you wrote in a comment, but properly testing head for an empty value, and explicitly using { print }. – Kusalananda Apr 7 at 7:21
  • @DarkHeart Now with added sed. – Kusalananda Apr 7 at 7:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.