74

This is what I'm doing (simplified example):

gsed -i -E 's/^(?!foo)(.*)$/bar\1/' file.txt

I'm trying to put bar in front of every line that doesn't start with foo. This is the error:

gsed: -e expression #1, char 22: Invalid preceding regular expression

What's wrong?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
yegor256
  • 102,010
  • 123
  • 446
  • 597
  • possible duplicate of http://stackoverflow.com/questions/2086450/pcre-regex-to-sed-regex – hostmaster Aug 29 '12 at 11:41
  • 1
    please update the chosen answer if possible. [This](http://stackoverflow.com/a/12178023/131120) shows that it's actually possible to achieve the same with sed. – erikbstack Jun 11 '15 at 09:39
  • I disagree with that. I was looking for how to do a lookahead with sed and the answer to that question is: you can't – Cecile Apr 01 '20 at 10:06

3 Answers3

172
sed -i '/^foo/! s/^/bar/' file.txt
  • -i change the file in place
  • /^foo/! only perform the next action on lines not ! starting with foo ^foo
  • s/^/bar/ change the start of the line to bar  
bschlueter
  • 3,817
  • 1
  • 30
  • 48
kkeller
  • 3,047
  • 3
  • 15
  • 11
  • 8
    Very nice solution. No need for back tracking. Just use functionality that sed already has. – D.Shawley Mar 06 '16 at 22:43
  • 13
    This is fine for an anchored expression like `^foo`, but unfortunately doesn't extend to the general case of negative lookahead anywhere in a pattern. – tripleee Jul 06 '16 at 09:36
  • 3
    It does, however, extend when only one instance of the replacement exists in the line. For example, `'s/(?!foo)baz/bar/'` =>`'/foobaz/! s/baz/bar/'`. Perhaps a proper application of cleverness can extend this approach to lines containing, e.g., "foobaz baz". – Nathan Vance Apr 04 '18 at 20:49
  • @tripleee It is possible. Check [this answer](https://unix.stackexchange.com/questions/487725/sed-replace-word-except-when-word-is-preceded-by-a-specific-string/487758#487758) for ideas. – Weijun Zhou Dec 14 '18 at 02:39
  • @WeijunZhou Replacing the `foobaz` string with a unique string which doesn't occur anywhere in the input (which is feasible with a randomized string if it's long enough), then replacing the remaining `baz`es, then replacing back the unique string with `foobaz` is, again, a workaround which only works when the negative lookahead is a static string. – tripleee Dec 14 '18 at 06:05
  • If it is a fixed-length(which is required for lookbehind anyway) string pattern you can still embed the original string in the intermediate string so that the information is not lost and can be recovered later. – Weijun Zhou Dec 14 '18 at 07:33
  • you sir, are my hero. perfect solution. thanks alot. – devDomm Jul 16 '19 at 09:20
  • What if I needed to delete the line? This did not work for me: sed '/foo/! /bar/d' file.txt – Nabheet Jun 23 '21 at 14:48
  • 1
    @Nabheet: I'm not sure what lines you want deleted. Your code says "search lines which do not contain foo" and then, instead of giving a command, saying what to do with those lines, you give another filter. That can not work. If you want to delete all lines which do not contain foo, then use '/foo/! d' If you want to delete all lines which do not contain foo but do contain bar, then you need a block command after the first filter to apply a second filter: '/foo/! {/bar/d}' And beware, the original question was about lines starting with foo, you dropped that by removing the ^ – kkeller Jun 27 '21 at 16:49
  • Awesome thanks! you just confirmed what I ended up implementing. Not sure how I ended up here but it was a lot of trial and error. I ended up with this /match_a/{/match_b/!d}. I needed "search lines for match_a, only delete line if it does NOT contain match_b." This is really cool and I didn't need any regex lookarounds. Thank you! – Nabheet Jun 28 '21 at 17:12
72

As far as I know sed has not neither look-ahead nor look-behind. Switch to a more powerful language with similar syntax, like perl.

Birei
  • 35,723
  • 2
  • 77
  • 82
  • 30
    A simple `sed` to `perl` conversion for reference: `sed 's/before/after/g' /my/file` => `cat /my/file | perl -ne 's/before/after/g; print;'` – Batandwa Jan 13 '14 at 18:06
  • 25
    @Batandwa The use of cat and piping into perl is redundant. Just use `perl -pi -e 's/before/after/g' /my/file`. – ypid Nov 23 '15 at 20:31
  • 3
    It's quite unfortunate that there is no lookahead. – NelsonGon Jun 20 '19 at 17:59
  • 3
    I think this is a wrong answer: `sed` has this capabilities supplying multiple editing sequences, as kkeller answer. – Cirelli94 Jul 09 '20 at 15:44
  • @Cirelli94: While this is a nice recommendation, that's not negative lookahead as in regular (pun intended) regex syntax. – v01pe Nov 20 '20 at 09:27
11

You use perl compatible regular expression (PCRE) syntax which is not supported by GNU sed. You should rewrite your regex according to SED Regular-Expressions or use perl instead.

Note that SED doesn't have lookahead and therefore doesn't support the regex feature you were trying to use. It can be done in SED using other features, as others have mentioned.

jvriesem
  • 1,859
  • 3
  • 18
  • 40
hostmaster
  • 1,822
  • 16
  • 17