Does lookbehind work in sed?
Solution 1
GNU sed does not have support for lookaround assertions. You could use a more powerful language such as Perl or possibly experiment with ssed
which supports Perl-style regular expressions.
perl -pe 's/(?<=foo)bar/test/g' file.txt
Solution 2
Note that most of the time you can avoid a lookbehind (or a lookahead) using a capture group and a backreference in the replacement string:
sed 's/\(foo\)bar/\1test/g' file.txt
Simulating a negative lookbehind is more subtile and needs several substitutions to protect the substring you want to avoid. Example for (?<!foo)bar
:
sed 's/#/##/g;s/foobar/foob#ar/g;s/bar/test/g;s/foob#ar/foobar/g;s/##/#/g' file.txt
- choose an escape character and repeat it (for example
#
=>##
). - include this character in the substring you want to protect (
foobar
here, =>foob#ar
orba
=>b#a
). - make your replacement.
- replace
foob#ar
withfoobar
(orb#a
withba
). - replace
##
with#
.
Obviously, you can also describe all that isn't foo
before bar
in a capture group:
sed -E 's/(^.{0,2}|[^f]..|[^o].?)bar/\1test/g' file.txt
But it will quickly become tedious with more characters.
Matheus Gontijo
Hello, I'm Matheus Gontijo 👋👋 ➡️ Shopware Developer Advocate @ WSNYC 🔥 +11 years working with e-commerce 🎤 Speaking and attending conferences all over the world! 🇺🇸🇨🇦🇩🇪🇧🇷🇳🇱🇧🇪🇭🇷🇫🇷🇬🇧 ✍🏻 Writing technical blogposts about Shopware and best practices ❤️ Clean code, Software Architecture, OOP, DDD, Agile 🗣️ Organized PHP Day Brazil 2018. +200 PHP developers 👉 Big fan of open-source, conferences, community, PHP, Symfony, Linux, remote work & soccer Let's have a conversation! 💬 💬 💬 Email - matheus at matheusgontijo.com Twitter - @mhgontijo LinkedIn - Matheus Gontijo GitHub - matheusgontijo Site - matheusgontijo.com
Updated on June 28, 2022Comments
-
Matheus Gontijo almost 2 years
I created a test using
grep
but it does not work insed
.grep -P '(?<=foo)bar' file.txt
This works correctly by returning
bar
.sed 's/(?<=foo)bar/test/g' file.txt
I was expecting
footest
as output, but it did not work. -
ikegami over 9 yearsThe text accompanying your solution doesn't quite make sense since Perl doesn't support PCRE either (at least not natively).
-
Max over 5 yearsBut this does not work for "negative" lookbehind, e.g. you want "bar" NOT preceded by "foo" to be replaced with "test", what would be done (if it worked) with /(?<!foo)bar/test/. Has anyone a solution to this? (I want to use uniq on the 5th field of an SQL file but preceding fields may contain spaces so I have no better idea than to replace all spaces NOT between ...', '... by "_"...)
-
Casimir et Hippolyte almost 4 years@Max: 1) choose an escape character and repeat it (for example
#
=>##
). 2) include this character in the substring you want to protect (foobar
here, =>foob#ar
). 3) make your replacement. 4) replacefoob#ar
withfoobar
. 5) replace##
with#
. Example with sed:sed 's/#/##/g;s/foobar/foob#ar/g;s/bar/test/g;s/foob#ar/foobar/g;s/##/#/g' <<<'abc foobar # foob#ar foo bar'
-
Max almost 4 yearsOK, yes that works, essentially you remove what you want to protect (maybe
foobar
=>-#-
(instead foob#ar) would be clearer), then you find & replace all others, then you put the "protected" ones back -
Casimir et Hippolyte almost 4 years@Max:
foobar
=>-#-
: if you want (and only if you have replaced all#
with##
before). -
Casimir et Hippolyte over 2 years@MichaelChirico: Thanks Chirico (and the sorceress) for your edit.