Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I cannot seem to make it work. GNU sed documentation says to escape the pipe, but that doesn't work, nor does using a straight pipe without the escape. Adding parens makes no difference.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog
share|improve this question

3 Answers 3

up vote 5 down vote accepted

By default sed uses POSIX Basic Regular Expressions, which don't include the | alternation operator. Many versions of sed, including GNU and FreeBSD, support switching into Extended Regular Expressions, which do include | alternation. How you do that varies: GNU sed uses -r, while FreeBSD, NetBSD, OpenBSD, and OS X sed use -E. Other versions mostly don't support it at all. You can use:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

and it will work on those BSD systems, and sed -r with GNU.


GNU sed appears to have totally undocumented but working support for -E, so if you have a multi-platform script that is confined to the above that is your best option. Since it isn't documented you probably can't really rely on it, though.

A comment notes that the BSD versions support -r as an undocumented alias too. OS X still doesn't today and the older NetBSD and OpenBSD machines I have access to don't either, but the NetBSD 6.1 one does. The commercial Unices I can reach universally don't. So with all that the portability question is getting pretty complicated at this point, but the simple answer is to switch to awk if you need it, which uses EREs everywhere.

share|improve this answer
    
The three BSDs you mentioned all support the -r option as a synonym of -E for compatibility with GNU sed. OpenBSD's and OS X's sed -E will interpret the escaped pipe as a literal pipe, not as alternation operator. Here's a working link to the NetBSD man page and here's one for OpenBSD that isn't ten years old. –  damien Jul 19 at 3:08
    

This happends because (a|b) is a regular expression use -r option to deal with this:

echo 'cat
dog
pear
banana
cat
dog'|sed -r 's/cat|dog/Bear/g'

from sed manpage:

-r, --regexp-extended

          use extended regular expressions in the script.
share|improve this answer

The portable way to do this - and the more efficient way - is with addresses. You can do this:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

In this way if the line does not contain the string cat and does not contain the string dog sed branches out of the script, autoprints its current line and pulls in the next to begin the next cycle. It therefore does not perform the next instruction - which in this example changes the entire line to read Bear but it could do anything.

It's probably worth noting also that any statement following the !b in that sed command can only match on a line containing either the string dog or cat - so you can perform further tests without any danger of matching a line that doesn't - which means you can now apply rules to only one or the other as well.

But that's next. Here's output from the above command:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

You can also portably implement a lookup table with backreferences.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

It's a lot more work to setup for this simple example case, but it can make for much more flexible sed scripts in the long run.

In the first line I exchange hold space and pattern space then insert the string <space>cat<space>dog<space> into hold space before exchanging them back.

From then on and on every following line I Get hold space appended to pattern space, then check to see if all of the characters from the beginning of the line until the newline I just added at the end match a string surrounded by spaces after it. If so I replace the entire lot with Bear and if not there is no harm done because I next Print only up to the first occurring newline in pattern space then delete it all.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

And when I say flexible, I mean it. Here it is replacing cat with BrownBear and dog with BlackBear:

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

You can of course expand a great deal on the contents of the lookup table - I picked up the idea from Greg Ubben's usenet emails on the subject when, in the 90's, he described how he constructed a crude calculator out of a single sed s/// statement.

share|improve this answer
1  
phew, +1. You have a penchant for thinking out of the box I must say –  1_CR Jul 19 at 4:52
    
@1_CR - See my last edit - not my idea - which is not to say that I don't appreciate that and consider it a compliment. But I like to give credit where it's due. –  mikeserv Jul 19 at 5:04

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.