You want to use negative lookbehind like this:

\w*(?<!foo)bar

Where (?<!x) means "only if it doesn't have "x" before this point".

See Regular Expressions - Lookaround for more information.

Edit: added the \w* to capture the characters before (e.g. "beach").

Answer from Adam Rofer on Stack Overflow
๐ŸŒ
Reddit
reddit.com โ€บ r/learnpython โ€บ regex match pattern not preceded by either of two expressions
r/learnpython on Reddit: Regex match pattern not preceded by either of two expressions
September 17, 2019 -

I'm a RegEx Noob and I'm using the regex (i.e. not re, for all that matters) to match a string not preceded by either of two expression.

So the regex should not match

foo bar
baz bar

but should match bar in

blub bar

I know how to match a string not preceded by something. That would be:

(?<!foo )bar

But I can't use

(?<!foo|baz )bar

because Python doesn't support variable width lookbehinds. From StackOverflow I learned that I could invert my string and and use a negative lookahead, but isn't there a less weird solution in my case?

oof(?! rab| zad)
Discussions

regex - Match only if not preceded or followed by a digit - Stack Overflow
Edit: I'm not trying to "match the whole word", because it is not sufficient that the match is preceded by and followed by a space. See the 3rd example of above that should also return a match. I want to match only instances where the match is not preceded or followed by a digit. More on stackoverflow.com
๐ŸŒ stackoverflow.com
April 30, 2017
regular expression - Match one regex pattern if not preceded by another one - Vi and Vim Stack Exchange
Finally, the issue with your negative ... that are preceded by the token in the item. Correcting the issues mentioned above, together with a few more optimizations (e.g. using 0?1 to match 1 or 01 at the end, using an explicit $ to ensure that's the end of the line, skipping capture groups where they're not needed), I got to this regex which seems ... More on vi.stackexchange.com
๐ŸŒ vi.stackexchange.com
December 24, 2021
Regex match exact word not preceded or followed by other characters
im trying to make a regex for matching a set of words. For example, if i am matching a set of words - American Tea Then in the string American Tea is awesome. Do you like American Tea? love More on stackoverflow.com
๐ŸŒ stackoverflow.com
Not preceded by either or
Got an answer in r/learnpython : https://www.reddit.com/r/learnpython/comments/d5g4ow/regex_match_pattern_not_preceded_by_either_of_two/ More on reddit.com
๐ŸŒ r/regex
3
2
September 17, 2019
๐ŸŒ
Python documentation
docs.python.org โ€บ 3 โ€บ library โ€บ re.html
re โ€” Regular expression operations โ€” Python 3.14.6 ...
May 25, 2026 - For example, Isaac (?!Asimov) will ... itโ€™s not followed by 'Asimov'. ... Matches if the current position in the string is preceded by a match for ... that ends at the current position. This is called a positive lookbehind assertion. (?<=abc)def will find a match in 'abcdef', since the lookbehind will back up 3 characters and check if the contained pattern matches. The contained pattern must only match strings of some fixed length, meaning that abc or a|b are allowed, ...
๐ŸŒ
O'Reilly
oreilly.com โ€บ library โ€บ view โ€บ regular-expressions-cookbook โ€บ 9781449327453 โ€บ ch05s06.html
5.6. Find Any Word Not Preceded by a Specific Word - Regular Expressions Cookbook, 2nd Edition [Book]
August 27, 2012 - You want to match any word that is not immediately preceded by the word cat, ignoring any whitespace, punctuation, or other nonword characters that come between. Lookbehind lets you check if text appears before a given position. It works by instructing the regex engine to temporarily step backward in the string, checking whether something can be found ending at the position where you placed the lookbehind. See Recipe 2.16 if you need to brush up on the details of lookbehind. The following ...
Authors ย  Jan GoyvaertsSteven Levithan
Published ย  2012
Pages ย  609
๐ŸŒ
Regular-Expressions.info
regular-expressions.info โ€บ lookaround.html
Regex Tutorial: Lookahead and Lookbehind Zero-Length Assertions
(?<!a)b matches a โ€œbโ€ that is not preceded by an โ€œaโ€, using negative lookbehind. It doesnโ€™t match cab, but matches the b (and only the b) in bed or debt. (?<=a)b (positive lookbehind) matches the b (and only the b) in cab, but does not match bed or debt. The construct for positive lookbehind is (?<=text): a pair of parentheses, with the opening parenthesis followed by a question mark, less than sign, and an equals sign.
Find elsewhere
Top answer
1 of 1
4

It's very nice that you gave this fairly complex regex a good try! There are some issues with it which are fairly hard to spot, so learning how to debug a regex would come handy.

To debug a regex, it's very useful to enable the 'hlsearch' and 'incsearch' options, which allow you to visually see what the regex actually matched and to highlight matches while you're writing a regex, which allows you to quickly spot and correct mistakes on the spot. So start by enabling those (possibly permanently, in your vimrc):

:set hlsearch incsearch

Then, it's often useful to compose a complex regex in a search (with the / command) rather than in a :s. First use a search to make sure it's matching what you want it to match, then you can use that same regex in a different context (such as the :s you want to use it in.)

Now, regarding your particular regex, one issue that's somewhat pervasive is you're using the \v "very magic" mode (which is a great idea here!) but you seem to be mixing up situations that need a backslash escape under \v (such as \{ and \} to match literal curly braces, and to a minor extent \. to match a literal dot instead of any character) and those that need to not have a backslash (such as \+ should be + to repeat one or more newlines, or \( and \) around the capture group for the month, it should be ( and ) instead, the version escaped with backslashes will match a literal pair of parens instead.)

You can also simplify your regex quite a bit by using the shorthand \d for a digit, or \w for a word character (alphabetic or numeric), which you could use instead of the [0-9] and [a-z0-9] groups (would also allow you to skip \c to force case insensitive matching.)

Finally, the issue with your negative match to prevent matches right after another item is that you're using the @! operator, which matches forward for that expression, while what you really want is @<!, which matches backward, to rule out matches that are preceded by the token in the item.

Correcting the issues mentioned above, together with a few more optimizations (e.g. using 0?1 to match 1 or 01 at the end, using an explicit $ to ensure that's the end of the line, skipping capture groups where they're not needed), I got to this regex which seems to be working as expected:

/\v(\\item \w*\n+)@<!\\item \\macro\{this\\var\}\.\% \d+-(\d+)-0?1$

You can then use this regex into a :s command. There are two issues that should be fixed in your attempt. The first is that the month is actually in capture group \2, since capture group \1 is the negative look-behind match. (You could also use %( and ) for expressions that need to be inside parens but shouldn't be capture groups, but using \2 instead of \1 is a simple enough fix in this case.)

Second one is that, to repeat the matched line, you can simply use & in the replacement side, that will preserve the matched contents exactly as they appear. (In the command you were working on, you seemed to stop at \macro{this\var}, so it's not totally clear whether you wanted to drop the last part... Your example does include it verbatim, in which case & would have been appropriate.)

Putting it all together:

:%s/\v(\\item \w*\n+)@<!\\item \\macro\{this\\var\}\.\% \d+-(\d+)-0?1$/\\item The month is \2\r&/

Hopefully the fixed regex and :s command will work well for you, and also the hints in this answer will in the future help you compose and debug complex regexes such as the one needed for this particular match.

Top answer
1 of 4
2

Use .replace() for fun and profit

/(?:^|\s)(american tea)/ig

https://regex101.com/r/qB0uO2/1

if you want to account for prefixes AND suffixes:

/(?:^|\s)(american tea)(?:\W|$)/ig 

https://regex101.com/r/qB0uO2/2

JSBIN EXAMPLE

var str = "American Tea is awesome. Do you like American Tea? love WowAmerican Tea #American Tea";

str.replace(/(?:^|\s)(american tea)(?:\W|$)/ig, function(i, m){
  console.log(m);
});

//"American Tea"
//"American Tea"

EDIT:

The above returns only the matches, if instead you want to preserve the capturing and matching prefixes and suffixes use capturing-groups for them aswell:

var str = "American Tea is awesome. Do you like American Tea? love WowAmerican Tea #American Tea";

var newStr = str.replace(/(^|\s)(american tea)(\W|$)/ig, function(im, p1, p2, p3){
  return  p1 +"<b>"+ p2 +"</b>"+ p3; // p1 and p3 will help preserve the pref/suffix
});

document.getElementById("result").innerHTML = newStr;
<div id="result"></div>

where the parts

  • p1 is the first matching group (any prefix)
  • p2 is the second matching group (the "American Tea" word)
  • p3 is the third matching group (any suffix)
2 of 4
0

You don't need regexes to match words.

I know a very neat CoffeeScript snippet :

wordList = ["coffeescript", "eko", "talking", "play framework", "and stuff", "falsy"]
tweet = "This is an example tweet talking about javascript and stuff."

wordList.some (word) -> ~tweet.indexOf word # returns true

Which compiles into the following javascript :

var tweet, wordList;

wordList = ["coffeescript", "eko", "talking", "play framework", "and stuff", "falsy"];

tweet = "This is an example tweet talking about javascript and stuff.";

wordList.some(function(word) { // returns true
  return ~tweet.indexOf(word); 
});

~ is not a special operator in CoffeeScript, just a cool trick. It is the bitwise NOT operator, which inverts the bits of its operand. In practice it equates to -x-1. Here it works on the basis that we want to check for an index greater than -1, and -(-1)-1 == 0 evaluates to false.

If you want the words that are matched, use :

wordList.filter (word) -> ~tweet.indexOf word # returns : [ "talking", "and stuff" ]

Or the same in JS :

wordList.filter(function(word) { // returns : [ "talking", "and stuff" ]
  return ~tweet.indexOf(word);
});
๐ŸŒ
Reddit
reddit.com โ€บ r/regex โ€บ not preceded by either or
r/regex on Reddit: Not preceded by either or
September 17, 2019 -

I'm a RegEx Noob and I'm using Python to match a string not preceded by either of two expression.

So the regex should not match

foo bar
baz bar

but should match bar in

blub bar

I know how to match a string not preceded by something. That would be:

(?<!foo )bar

But I can't use

(?<!foo|baz )bar

because Python doesn't support variable width lookbehinds. From StackOverflow I learned that I could invert my string and and use a negative lookahead, but isn't there a less weird solution in my case?

oof(?! rab| zad)
๐ŸŒ
TutorialsTeacher
tutorialsteacher.com โ€บ regex โ€บ lookarounds
Lookarounds in Regex
The following finds any word that is not followed by a question mark ? character. ... In the above example, \b is used for a word boundary, \w+ is used for one or more word characters, so \b\w+\b finds the whole word. Pattern, \? uses escape sequence \ for ? because it is a metacharacter in regex so to find out literal ?, we must use an escape character. Positive lookbehind matches a pattern only if it is preceded by another pattern but does not include the preceding pattern in the matching result.
๐ŸŒ
Notepad++ Community
community.notepad-plus-plus.org โ€บ topic โ€บ 25447 โ€บ regex-find-string-in-html-not-at-line-start-or-following-p
Regex: Find String in HTML Not at Line Start or Following </p> | Notepad++ Community
February 7, 2024 - @Sylvester-Bullitt ^Achtung(*SKIP)(*F)|<p>Achtung(*SKIP)(*F)|Achtung will find the word Achtung except if it is at the beginning of the line or if it is preceded by a <p> You may also use ^Achtung(*SKIP)(*F)|<p[^<>]*>Achtung(*SKIP)(*F)|<q>Achtung(*SKIP)(*F)|<p><q>Achtung(*SKIP)(*F)|Achtung which will skip every <p................................>, <q> and <p><q> if they are followed by the word Achtung but will find the word Achtung otherwise.
๐ŸŒ
Regex Tester
regextester.com โ€บ 107904
Regex to match string NOT followed by [ - Regex Tester/Debugger
Regex Tester is a tool to learn, build, & test Regular Expressions (RegEx / RegExp). Results update in real-time as you type. Roll over a match or expression for details.
๐ŸŒ
Alex Hyett
alexhyett.com โ€บ regular-expressions
Finally Understand Regular Expressions: Regex isn't as hard as it looks | Alex Hyett
January 10, 2023 - This will only match the at in cat but not any of the other occurrences. Similarly, we can find the negative version of the positive lookbehind by changing the = to a !. If we now search for (?<!c)at it will match on the at in sat, mat and at. It is also possible to combine multiple patterns in one regular expression. Let's say we want to find all the a characters that aren't preceded by an s as well as all the t characters. We can do an OR symbol | and have a pattern that looks like this: