sed command to exclude specific lines in file between range of pattern

Viewed 480

I want to do substitution using sed command, but also this should not get applied to specific range of lines matching some pattern/word.

File has below lines:

startline1
top
nice
good
bad
nice
nice
verybad
Extremely bad
bad
nice
endline1
nice

Trail from my end: sed -i '/startline1/,/endline1/ s/nice/decent/' The substitute must happen between startline1 and endline1 But must exclude the update between bad and verybad words

With the above command I get result as below:

startline1
top
decent
good
bad
decent
decent
verybad
Extremely bad
bad
decent
endline1
nice
  • This will do a replacement between only startline1 and endline1, which is as expected.
  • But now I don't want the update to happen between bad and verybad.

Expected Result:

startline1
top
decent
good
bad
nice
nice
verybad
Extremely bad
bad
decent
endline1
nice

Any suggestion on this would be of help.

2 Answers

Could you please try following based on your shown samples. Tested successfully in link https://ideone.com/zFl8LK

awk '
/^bad$/{
 bad_found=1
}
/^verybad$/{
 bad_found=""
}
/endline1/{
  found=""
}
/startline1/{
  found=1
}
found && !bad_found{
  sub(/nice/,"decent")
}
1
' Input_file

Explanation: Firstly checking condition if line starts with bad set bad_found then checking if line starts with verybad then unset bad_found there checking condition if a line contains endline1 then unset found(found variable work is explained further in explanation). Then checking condition if line contains startline1 then setting variable found here. Then checking condition if found is set AND variable bad_found is NOT SET then substitute nice with decent in that line(where found makes sure that we are boundaries of startling to endline). Finally mentioning 1 will print all lines.

Note: in case your Input_file's lines may have more than 1 occurrence of nice then change sub to gsub in above code.

This might work for you (GNU sed):

sed '/\n/bb;/^start/{:a;N;/^end/M!ba;:b;/^bad.*verybad/h;x;/./{/^verybad/z;s/[^\n]*\n//;x;P;D};x;s/^nice/decent/;P;D}' file

The lines between start and end are gathered up and then processed a line at a time.

If the pattern space contains multiple lines i.e. the lines have already been gathered up, a flag is set in the hold space by copying the current pattern space if the first of these lines contains bad and there follows a line that contains verybad.

If the hold space is not empty and first line is verybad the hold space is emptied, thus flipping the s/nice/decent/ on.

N.B. The solution uses the control flow that the D command resumes processing the current multi line until the pattern space is empty.

If more that one nice occurs on a line use the idiom:

`:c;s/^\([^\n]*\)nice/\1decent/;tc`
Related