Search code examples
unixscriptingsedtext-manipulation

excluding first and last lines from sed /START/,/END/


Consider the input:

=sec1=
some-line
some-other-line

foo
bar=baz

=sec2=
c=baz

If I wish to process only =sec1= I can for example comment out the section by:

sed -e '/=sec1=/,/=[a-z]*=/s:^:#:' < input

... well, almost.

This will comment the lines including "=sec1=" and "=sec2=" lines, and the result will be something like:

#=sec1=
#some-line
#some-other-line
#
#foo
#bar=baz
#
#=sec2=
c=baz

My question is: What is the easiest way to exclude the start and end lines from a /START/,/END/ range in sed?

I know that refining the "s:::" clause can give a solution in this specific case, but I am after the generic solution here.

In "Sed - An Introduction and Tutorial" Bruce Barnett writes: "I will show you later how to restrict a command up to, but not including the line containing the specified pattern.", but I was not able to find where he actually show this.

In the "USEFUL ONE-LINE SCRIPTS FOR SED" Compiled by Eric Pement, I could find only the inclusive example:

# print section of file between two regular expressions (inclusive)
sed -n '/Iowa/,/Montana/p'             # case sensitive

Solution

  • This should do the trick:

    sed -e '/=sec1=/,/=sec2=/ { /=sec1=/b; /=sec2=/b; s/^/#/ }' < input
    

    This matches between sec1 and sec2 inclusively and then just skips the first and last line with the b command. This leaves the desired lines between sec1 and sec2 (exclusive), and the s command adds the comment sign.

    Unfortunately, you do need to repeat the regexps for matching the delimiters. As far as I know, in "standard" sed there's no better way to do this. At least you can keep the regexps clean, even though they're used twice.

    This is adapted from the SED FAQ: How do I address all the lines between RE1 and RE2, excluding the lines themselves?

    GNU sed only

    As mentioned in the comments, this more concise version will work for GNU sed, as long as you are only targeting OSes where the GNU version is certain to be present:

    sed '/=sec1=/,/=sec2=/ { //! s/^/#/ }'
    

    Using the sed on BSD derivatives (including macOS), you'll get this error message, though:

    sed: 1: "/=sec1=/,/=sec2=/ { //! ...": bad flag in substitute command: '}'
    

    On your system, GNU sed may be available as gsed. This is the case if, for example, you use MacPorts on macOS.