1

There is a similar question: sed search a range and print first set , but for the life of me, I can't understand how to adapt it to my case.

If I have a config file defined approximately like this:

define global {
alias=main
icon_size=20
object_id=0
map_image=net_map_only.jpg
grid_show=0
}

define line {
x=6c4e2f%+10,5dd361%+10
y=6c4e2f%+10,5dd361%+10
z=90
object_id=2d6bec
view_type=line
line_type=11
line_width=1
line_color=#000000
}

#define line {
#x=266424%+10,604bc6%+10
#y=266424%+10,604bc6%+10
#z=90
#object_id=5b6082
#view_type=line
#line_type=11
#line_width=1
#line_color=#000000
#}

define host {
host_name=INTERNET
x=519
y=307
z=98
object_id=6c4e2f
iconset=std_medium
icon_size=20
label_show=1
label_background=#ffffff
label_border=transparent
label_maxlen=14
}


define host {
host_name=INTERNET
x=519
y=307
z=98
object_id=6c4e2f
iconset=std_medium
icon_size=20
label_show=1
label_background=#ffffff
label_border=transparent
label_maxlen=14
}

There are multiple host entries as you can see above and a lot of other entries (define line, define global, etc). Entry is defined by starting with regex "^define host" and up to next "}".

For debugging purposes I want to selectively comment or delete the first host entry only. As I will iterate through each other host entry and check for errors, I would like to do it from command line.

My initial thought was sed, with:

sed -i '/^define host/,/}/s/^/#/' config.txt

which perfectly comments all host config lines.

However, I can't seem to get it, how to adapt it so that it only does this for the first matching config entry only. Is it even possible?

Gnudiff
  • 4,297
  • 1
  • 24
  • 25
  • I have modified your example so that it contains multiple `host` entries (this is what I think you have). – Arkadiusz Drabczyk Sep 05 '19 at 16:41
  • @ArkadiuszDrabczyk I have added more info, so that real structure can be seen. – Gnudiff Sep 05 '19 at 17:16
  • you should always provided *correct* example input when writing the question. Otherwise other users will write answers that won't work for you and we'll waste everybody's time. – Arkadiusz Drabczyk Sep 05 '19 at 18:00
  • @ArkadiuszDrabczyk I thought the context was clear from the rest of the text of the question (that there can be other types of entries and that host entry starts by ^define host). Apparently it wasn't. – Gnudiff Sep 05 '19 at 18:11

4 Answers4

3

IIUC:

sed '1,/\}/{s/^/#/}' config.txt

Example output:

#define host {
#host_name=INTERNET
#x=519
#y=307
#z=98
#object_id=6c4e2f
#iconset=std_medium
#icon_size=20
#label_show=1
#label_background=#ffffff
#label_border=transparent
#label_maxlen=14
#}

define host {
host_name=INTERNET
x=519
y=307
z=98
object_id=6c4e2f
iconset=std_medium
icon_size=20
label_show=1
label_background=#ffffff
label_border=transparent
label_maxlen=14
}

define host {
host_name=INTERNET
x=519
y=307
z=98
object_id=6c4e2f
iconset=std_medium
icon_size=20
label_show=1
label_background=#ffffff
label_border=transparent
label_maxlen=14
}

OP modified example input and stated that define host { doesn't have to at the top of the file. In such case the following command should work:

sed '/^define host/{:a /^$/{:b ;n;bb}; s/^/#/;;n;ba}' config.txt

Example output based by sample input provided the OP:

define global {
alias=main
icon_size=20
object_id=0
map_image=net_map_only.jpg
grid_show=0
}

define line {
x=6c4e2f%+10,5dd361%+10
y=6c4e2f%+10,5dd361%+10
z=90
object_id=2d6bec
view_type=line
line_type=11
line_width=1
line_color=#000000
}

#define line {
#x=266424%+10,604bc6%+10
#y=266424%+10,604bc6%+10
#z=90
#object_id=5b6082
#view_type=line
#line_type=11
#line_width=1
#line_color=#000000
#}

#define host {
#host_name=INTERNET
#x=519
#y=307
#z=98
#object_id=6c4e2f
#iconset=std_medium
#icon_size=20
#label_show=1
#label_background=#ffffff
#label_border=transparent
#label_maxlen=14
#}


define host {
host_name=INTERNET
x=519
y=307
z=98
object_id=6c4e2f
iconset=std_medium
icon_size=20
label_show=1
label_background=#ffffff
label_border=transparent
label_maxlen=14
}
Arkadiusz Drabczyk
  • 11,227
  • 2
  • 25
  • 38
  • Thank you, but doesn't seem to work for me. Is there everything OK with apostrophes? Shouldn't the last one be }' instead of '} ? Perhaps it was not enough clear from the question that the host entry can be not the first define entry in file. I have added more lines so that a more real structure can be seen. – Gnudiff Sep 05 '19 at 16:52
  • 1
    It's ok, `'` can also be moved after `}`. I changed it. – Arkadiusz Drabczyk Sep 05 '19 at 16:53
2

This might work for you (GNU sed):

sed '/^define host/{x;s/^/x/;x};//,/^}/{x;/^x\{1\}$/{x;d};x}' file

Increment a counter in the hold space at the start of a range and delete the range depending on it e.g. this deletes the first occurrence of the range of lines from a line starting define host to a line starting }.

This solution can be extended to delete a range of ranges or a set of ranges:

sed '/^define host/{x;s/^/x/;x};//,/^}/{x;/^x\{2,3\}$/{x;d};x}' file

This would delete the second and third occurrences of the above range.

sed '/^define host/{x;s/^/x/;x};//,/^}/{x;/^\(x\{2\}\)*$/{x;d};x}' file

This would delete even occurrences of the above range.

potong
  • 55,640
  • 6
  • 51
  • 83
  • Thank you, I will check this later. While Arkadius' answer works, it only does so, because there is a double newline after end of range I want. In effect, it won't comment } if it were specified as end of range in a general solution. If your answer is generalizeable, it would be a great help dealing with all kinds of config files. – Gnudiff Sep 06 '19 at 07:00
  • 1
    @Gnudiff to comment ranges, replace the `d` command by `s/^/#/;b`. – potong Sep 06 '19 at 07:29
2

ed is great for scripting in place editing of a file without treating it a line at a time like sed and awk do:

ed -s foo.cnf <<EOF
/^define host/;/^}$/ s/^/#/
w
EOF

or in an interactive shell session

$ ed -s foo.cnf
/^define host/;/^}$/ s/^/#/
wq

will comment every line in the first block with a line starting with define host and ending with a line with nothing but }. Note how it's very similar to the sed you tried, but the address range only applies to the first matching block, not all matching blocks like it does in sed.

Shawn
  • 47,241
  • 3
  • 26
  • 60
1

sed is good in single-line manipulations. Although what you want, can be done with sed, it would be very painful.

Note, knowing sed well can be very useful, essentially it has a similar script language than awk. But awk is closer to the current higher-level languages, and it is easier to create complex filters with it.

If I understand your problem well, you only need to put a "#" before all the lines, until the first single-line "}". The "define host {" part is only redundant information (altrhough you can make it into the script if you wish to). It can be done easily with the following awk script:

awk '
  BEGIN {
    PREF="#";
  };

  {
    print PREF $0;
  };

  /^\}$/ {
    PREF="";
  };
'

The best practice of awk if you follow its logic:

  • you have a list of local variables, these define the internal state of your current program
  • for all the lines of the input, awk checks all your command blocks for the regexp before it. If it matches, it executes the block. The check-execute happens in the order they were declared in your awk script.
  • BEGIN is called always before reading the first line, END is called after the last line, and if you don't use a regexp before the block, it will be called for all the lines.

If you want, you can develop even complex software in awk, but not this is the awk is for.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • thank you I am quite willing to try AWK but tha question is how to fix the script so that it does find the first define host line because host entry can be at anywhere in the file – Gnudiff Sep 05 '19 at 16:23
  • 1
    @Gnudiff Wow, also a hardcore sed solution is arrived! Nice. Learn sed deeply, it can be very useful :-) – peterh Sep 05 '19 at 16:41