3

Suppose my file looks like this:

foo  bar
 foo    bar bar
foo foo      bar  foo

What I want to do is delete every foo that is followed by a bar (i.e. there is only whitespace between them), without changing any of the whitespace. Down below, I have replaced all the characters that should be deleted with underscores to make this clear:

___  bar
 ___    bar bar
foo ___      bar  foo

The command should result in the following file:

  bar
     bar bar
foo       bar  foo

How could I achieve this with sed or awk?

3 Answers 3

3

OK, I found out how to do this. The command to do this with sed would be:

sed -i -E 's/foo([[:blank:]]*)bar/\1bar/' file
3

With anything which supports perl-compatible regular expressions (PCRE), you can use a positive lookahead:

perl -pe 's/foo(?=\s*bar)//' < txt

(?=) is a "zero-width positive lookahead assertion".

It needs to match for the whole RE to match, but it doesn't include what it matches, so you don't need to capture and re-insert the matched text.

There are also negative lookahead, and positive and negative lookbehinds, all collectively called "lookarounds".

4
  • Should probably be /foo(?=.+bar)/, going by the description in the question, since your version will also change foobar (with zero spaces) to bar. Commented May 29, 2023 at 22:13
  • @JanusBahsJacquet Probably depends on whether "only whitespace" means "1 or more whitespace characters" or "0 or more whitespace characters". OP's own answer uses *, so I'll err on the side of "0 or more", but indeed, if it should be "1 or more" it would be +.
    – jcaron
    Commented May 30, 2023 at 10:32
  • 1
    Do you think perl -pe 's/foo(?=\s*bar)//' might be better? With the \s* atom you avoid problems with matches later in the line, so that lines like foo and some other words then bar remain untouched. Commented Jul 2, 2023 at 16:15
  • 1
    @jubilatious1 ah indeed, I had missed the “if there’s only white space between them”. Fixed.
    – jcaron
    Commented Jul 2, 2023 at 18:51
2

Using Raku (formerly known as Perl_6)

~$ raku -pe 's:g/   foo <?before \s* bar> //;'   file

#OR

~$ raku -pe 's:g/ <(foo)>        \s* bar //;'  file

The answers above are coded in Raku, a member of the Perl-family of programming languages. Advantages of Raku include high-level Unicode support built-in, as well as a robust/refined Regex implementation. Above, Raku Regexes are whitespace-tolerant in the recognition domain (left half of s///), so Regex atoms can be spread-out (and lined up). Also, all Regex modifiers such as :global (or :g) move to the head of the s/// operator in Raku. Like Perl, the global flag or "adverb" allows more than one match per line.

The first answer is a rough translation of @jcaron's excellent Perl(5) answer. Note in Raku, positive lookaheads are spelled <?before ... >. The second answer uses Raku's <(...)> capture markers, so that after all three atoms match, only foo is retained in the capture (and deleted in the replacement).

Both answers specifically change foo before bar with only whitespace in-between. This is an important point: given the phrase "my valentine, my bloody valentine" the Raku code s:g/my <?before \s* valentine> // will remove the first "my" (because "my-is_before-valentine"), but the second "my" remains untouched.

https://docs.raku.org/language/regexes
https://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .