42

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.

Samuel Harmer
  • 4,016
  • 4
  • 29
  • 66
Matheus Gontijo
  • 959
  • 1
  • 11
  • 28

2 Answers2

42

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
hwnd
  • 67,942
  • 4
  • 86
  • 123
  • The text accompanying your solution doesn't quite make sense since Perl doesn't support PCRE either (at least not natively). – ikegami Sep 30 '14 at 05:57
34

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.

Casimir et Hippolyte
  • 85,718
  • 5
  • 90
  • 121
  • 6
    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 /(? – Max Nov 18 '18 at 16:59
  • @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' << – Casimir et Hippolyte Jun 11 '20 at 18:36
  • 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 – Max Jun 11 '20 at 18:42
  • @Max: `foobar` => `-#-`: if you want (and only if you have replaced all `#` with `##` before). – Casimir et Hippolyte Jun 11 '20 at 18:49
  • @MichaelChirico: Thanks Chirico (and the sorceress) for your edit. – Casimir et Hippolyte Jan 11 '22 at 21:56