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

20,354

Solution 1

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 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?

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

you could also use awk

awk '/sec1/{f=1;print;next}f && !/sec2/{ $0="#"$0}/sec2/{f=0}1' file
Share:
20,354

Related videos on Youtube

Chen Levy
Author by

Chen Levy

Self motivated, autodidact, diligent documenter and a mentor. For as long as I can remember, I always was fascinated by technology. I am a long time #Python and #Linux developer, with experience in #Rust. Working with #Open Source technologies and contributing back. Over the years I filled roles in #systems, #backend and #devops in #storage and #security companies. Worked with #AWS, #Bash, #Docker, #ELK, #Flask, #Git, #PKI, #SQL and much more. I am an #autodidact. Constantly learning new techniques and technologies (such as #async/await, #FastAPI, #Haskell, etc). I also tend to disseminate knowledge: Diligent documentation writer, an experienced and well received instructor, both #mentoring one-on-one and as a public speaker. This is one of the reasons I consider myself one of the lucky few for whom their job is also their hobby and passion. Happiest when coding using Emacs. Successfully completed projects I wrote from scratch as well as took ownership and maintained legacy code. Consumed and designed #APIs (#RESTful and otherwise). I also worked with Networking (#TCP/IP) I am not a stranger to reading RFCs. Done development support work with dynamic languages (#Python, #Perl) and shell scripting (#Bash), created build and deployment systems (#CMake, GNU build tools), designed #CI/CD pipelines (#GitLab-CI, #Jenkins). A long time ago in a galaxy far away I also done some low level development with C/C++ and code migration, writing in both the Linux kernel and user space. Now a days prefer Rust for low level tasks.

Updated on August 16, 2020

Comments

  • Chen Levy
    Chen Levy almost 4 years

    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 for many cases refinement of the "s:::" claws can give 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
    
  • Chen Levy
    Chen Levy almost 15 years
    Yes, this exactly what I was looking for. Thanks.
  • orolo
    orolo over 11 years
    nice. also, the second version doesn't blow up php because of that pesky $.
  • mbigras
    mbigras about 7 years
    For folks on macOS with BSD sed you need to use actual newlines instead of semicolons. For more details check out this answer
  • Sundeep
    Sundeep almost 7 years
    not sure about other versions, but with GNU sed, this could be easily done using '/=sec1=/,/=sec2=/ { //! s/^/#/ }' ... from manual empty regular expression ‘//’ repeats the last regular expression match
  • Dan
    Dan about 6 years
    I have a hunch, they're not equivalent, should there be more than one of those ranges in the stream/file.
  • yugr
    yugr over 4 years
    @Sundeep Given that this is the most concise expression, why not post it as answer?