Reputation: 16408
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
Upvotes: 52
Views: 23541
Reputation: 29288
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?
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.
Upvotes: 45
Reputation: 897
You don't have to repeat any regular expression(s) to make this work.
$ sed -e '/^=sec1=$/{:0;n;/^=\w*=$/!{s/^/#/;b0}}' <<EOF
=sec1=
some-line
some-other-line
foo
bar=baz
=sec2=
c=baz
EOF
The output will be:
=sec1=
#some-line
#some-other-line
#
#foo
#bar=baz
#
=sec2=
c=baz
Taking apart the sed script, we have:
/^=sec1=$/
a regular expression address matching the opening section marker{:0;n;/^=\w*=$/!{s/^/#/;b0}}
a command block, as follows:
:0
a label to return to latern
print the current line, and read the next line/^=\w*=$/!
a regular expression address matching any line that isn't a section marker{s/^/#/;b0}
a command block, as follows:
s/^/#/
prepend a #
to the lineb0
branch to label 0
The inner loop between :0
and b0
continues looping until it encounters any line that is a section marker (or the end of the file).
Upvotes: 1
Reputation: 2003
I've used:
sed '/begin/,/end/{/begin\|end/!p}'
This will search all the lines between the patterns, then print everything not containing the patterns
Upvotes: 2
Reputation: 473
Another way would be
sed '/begin/,/end/ {
/begin/n
/end/ !p
}'
/begin/n
-> skip over the line that has the "begin" pattern
/end/ !p
-> print all lines that don't have the "end" pattern
Taken from Bruce Barnett's sed tutorial http://www.grymoire.com/Unix/Sed.html#toc-uh-35a
Upvotes: 7
Reputation: 3947
If you're not interested in lines outside of the range, but just want the non-inclusive variant of the Iowa/Montana example from the question (which is what brought me here), you can write the "except for the first and last matching lines" clause easily enough with a second sed:
sed -n '/PATTERN1/,/PATTERN2/p' < input | sed '1d;$d'
Personally, I find this slightly clearer (albeit slower on large files) than the equivalent
sed -n '1,/PATTERN1/d;/PATTERN2/q;p' < input
Upvotes: 13
Reputation: 343057
you could also use awk
awk '/sec1/{f=1;print;next}f && !/sec2/{ $0="#"$0}/sec2/{f=0}1' file
Upvotes: 2