Does lookbehind work in sed?

15,082

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 or ba => b#a).
  • make your replacement.
  • replace foob#ar with foobar (or b#a with ba).
  • 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.

Share:
15,082
Matheus Gontijo
Author by

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 &amp; 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, 2022

Comments

  • Matheus Gontijo
    Matheus Gontijo almost 2 years

    I created a test using grep but it does not work in sed.

    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
    ikegami over 9 years
    The text accompanying your solution doesn't quite make sense since Perl doesn't support PCRE either (at least not natively).
  • Max
    Max over 5 years
    But 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
    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) replace foob#ar with foobar. 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
    Max almost 4 years
    OK, 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
    Casimir et Hippolyte almost 4 years
    @Max: foobar => -#-: if you want (and only if you have replaced all # with ## before).
  • Casimir et Hippolyte
    Casimir et Hippolyte over 2 years
    @MichaelChirico: Thanks Chirico (and the sorceress) for your edit.