14

Using sed, how do I search for a line ending with foo and then edit the next line if it starts with #bar?

Or put another way, I want to remove a comment # from the next line if it starts with #bar and the previous line ends in foo.

For example:

This is a line ending in foo
#bar is commented out
There are many lines ending in foo
#bar commented out again

I tried:

sed -i 's/^foo\n#bar/foo\nbar/' infile

3 Answers3

21
sed '/foo$/{n;s/^#bar/bar/;}'

is a literal translation of your requirement. n is for next.

Now that doesn't work in cases like:

line1 foo
#bar line2 foo
#bar

Or:

line1 foo
line2 foo
#bar

As the line that is pulled into the pattern space by n is not searched for foo.

You could address it by looping back to the beginning after the next line has been pulled into the pattern space:

sed '
  :1
  /foo$/ {
     n
     s/^#bar/bar/
     b1
  }'
  • Is there any reason you didn't use P;D for one line only solution? – cuonglm May 24 '16 at 15:09
  • 2
    @cuonglm, it looks like the P;D approaches are already covered in other answers. This answer shows the direct translation of the requirement approach (search for foo$, get the next line, substitute) along with its limitations. – Stéphane Chazelas May 24 '16 at 15:17
  • Excelent solution(s). Could you explain why the first one fails in those special cases, and why the second one works? BTW: a one line equivalent to the second one would be: sed ':1;/foo$/{n;s/^#bar/bar/;b1}' – alx - recommends codidact Feb 07 '20 at 13:56
  • 1
    @CacahueteFrito, that wouldn't be portable/standard. You can't have anything after :1 or b1 because in many sed implementations including the original UNIX implementation, that anything would be taken as part the branching label's name (that even used to be required by POSIX). – Stéphane Chazelas Feb 07 '20 at 14:02
9

Use the N;P;D cycle and attempt to substitute each time:

sed '$!N;s/\(foo\n\)#\(bar\)/\1\2/;P;D' infile

this removes the leading # from #bar only if it follows a line ending in foo otherwise it just prints the pattern space unmodified.


Apparently, you want to uncomment US mirrors in /etc/pacman.d/mirrorlist which is a whole different thing:

sed -e '/United States/,/^$/{//!s/^#//' -e '}' /etc/pacman.d/mirrorlist

This will uncomment all mirrors in the US section in /etc/pacman.d/mirrorlist

don_crissti
  • 82,805
  • This prints the lines, but it is not removing the '#'. It may be my implementation. I am using Arch Linux and am attempting to remove all the comments from the United States servers in the mirrorlist file. – CompSci-PVT May 24 '16 at 14:09
  • It does for me (archlinux too) so there's something wrong with your file. Maybe you have spaces, try with sed '$!N;s/\(foo[[:blank:]]*\n\)#\([[:blank:]]*bar\)/\1\2/;P;D' infile – don_crissti May 24 '16 at 14:11
  • I was looking at this website and I commented out all the servers, and then wanted to only uncomment United States servers: https://wiki.archlinux.org/index.php/Mirrors#Sorting_mirrors – CompSci-PVT May 24 '16 at 14:26
  • @CompSci-PVT - lol, that's a whole different question dude... You want to uncomment all commented lines that follow a pattern inside a paragraph. See my updated post though you should edit the question to reflect your actual requirements. – don_crissti May 24 '16 at 14:44
2

Try:

sed -e '$!N;/foo\n#bar/s/\(\n\)#/\1/;P;D'
cuonglm
  • 153,898