papillon
papillon

Reputation: 2063

Find multiple occurrences of a character after another character

I need to find and replace multiple occurrences of a character after another character.

My file looks like this:

b
a
b
b

And I need to replace all b after a with c:

b
a
c
c

I came up with this: a((\n|.)*)b as the find expression and a$1c as the replace option, however it only replaces the last match instead of all of them.

I am using VSCode's global search and replace option.

I found a dirty way to achieve what I want: I add a ? lazy quantifier after .* matches once, and I apply the replacement. Then I can do it again and it will replace the next match. I do this until all occurrences are replaced.

However this would not be usable if there are thousands of matchs, and it would be very interesting to know if there is a proper way to do it, with only 1 find.

How can I match all b after a?

Upvotes: 2

Views: 1080

Answers (2)

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 626896

You can use

(?<=a[\w\W]*?)b

Replace with c. Details:

  • (?<=a[\w\W]*?) - a positive lookbehind that matches a location that is immediately preceded with a and then any zero or more chars (as few as possible)
  • b - a b.

Also, see Multi-line regular expressions in Visual Studio Code for more ways to match any char across lines.

Demo:

enter image description here

After replacing:

enter image description here

If you need to use something like this to replace in multiple files, you need to know that the Rust regex used in the file search and replace VSCode feature is really much less powerful and does not support neither \K, nor \G, nor infinite-width lookbehinds. I suggest using Notepad++ Replace in Files feature:

enter image description here

The (?:\G(?!\A(?<!(?s:.)))|a)[^b]*\Kb pattern matches

  • (?:\G(?!\A(?<!(?s:.)))|a) - either of the two options:
    • \G(?!\A(?<!(?s:.))) - the end of the previous successful match ((?!\A(?<!(?s:.))) is necessary to exclude the start of file position from \G)
    • | - or
    • a - an a
  • [^b]* - any zero or more occurrences of chars other than b
  • \K - omit the matched text
  • b - a b char.

Upvotes: 3

JvdV
JvdV

Reputation: 75860

It's probably not the prettiest, but when tried and tested the following worked for me:

(?:^a\n|\G(?<!\A))\n*\Kb$

See the online demo. I don't know VSCode but a quick search let me to believe it should follow Perl based PCRE2 syntax as per the linked demo.

  • (?: - Open non-capture group:
    • ^a\n - Start line anchor followed by "a" and a newline character.
    • | - Or:
    • \G(?<!\A) - Meta escape, assert position at end of previous match or start of string. The negative lookbehind prevents the start of string position to be matched.
    • ) - Close non-capture group.
  • \n* - 0+ new-line characters.
  • \K - Meta escape, reset starting point of reported match.
  • b$ - Match a literal "b", followed by an end-line anchor.

Upvotes: 3

Related Questions