RegEx for capturing contents between asterisks, except for a word

Viewed 352

How to extract the text between asterisks except “NOT” in this string provided that regular expression can only be used?

* apple *  * orange tasty *  * banana *  * grape NOT *

Expected results:

apple

orange tasty

banana
5 Answers

Did not read all answers, but Mr. Morton's answer is quite insightful.

The question is also very interesting with variety of ways to be approached, and we would be starting with an expression that collects our NOT words using a word boundary, altered by our desired outputs. Finally, we swipe from the start to the end of our string what might be left, especially if we wish to replace, yet this last part is unnecessary:

(?:(\bNOT\b).)*|([\w\s]+?)|(.*?)

Here, we have two undesired groups:

(?:(\bNOT\b).)*

and

(.*?)

and our desired data is in this second capturing group:

([\w\s]+?)

which can be simply modified if we have more desired chars than those listed in our char class: [\w\s].

RegEx

If this expression wasn't desired and you wish to modify it, please visit this link at regex101.com.

RegEx Circuit

jex.im visualizes regular expressions:

enter image description here

Demo

You might make use of SKIP FAIL and \K to forget what was currenly matched:

\*[^*]* NOT [^*]*\*(*SKIP)(*FAIL)|\*\s*\K\w+(?: \w+)*(?= \*)

Explanation

  • \*[^*]* NOT [^*]*\* Match NOT between asterix and space using a negated character class
  • (*SKIP)(*FAIL) Skip what is matched
  • | Or
  • \*\s*\K Match asterix, 0+ whitespace chars and forget what was matched
  • \w+(?: \w+)* Match 1+ word chars and a repeating pattern to match a space and 1+ word chars
  • (?= \*) Positive lookahead, assert what is on the right is a space and an asterix

Regex demo | grep demo

For example:

echo "* apple * * orange tasty * * banana * * grape NOT *" | grep -Po "\*[^*]* NOT [^*]*\*(*SKIP)(*FAIL)|\*\s*\K\w+(?: \w+)*(?= \*)"

Result

apple
orange tasty
banana

You can use this to also extract the other data in another group:

\*\s?(?:([a-zA-Z0-9 ]+?NOT))\s?\*|\*\s?([a-zA-Z0-9 ]+?)\s?\*

This also works in other regex engines. Check it out here.
Visual Embeded:

<iframe frameborder="0" width="746" height="562" src="https://jex.im/regulex/#!embed=true&flags=&re=%5C*%5Cs%3F(%3F%3A(%5Ba-zA-Z0-9%20%5D%2B%3FNOT))%5Cs%3F%5C*%7C%5C*%5Cs%3F(%5Ba-zA-Z0-9%20%5D%2B%3F)%5Cs%3F%5C*"></iframe>

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.

With GNU awk for multi-char RS and RT:

$ awk -v RS='[*][^*]+[*]' 'RT !~ /NOT/{print RT}' file
* apple *
* orange tasty *
* banana *

With any awk in any shell on every UNIX box:

$ awk '{ while ( match($0,/[*][^*]+[*]/) ) {str=substr($0,RSTART,RLENGTH); if (str !~ /NOT/) print str; $0=substr($0,RSTART+RLENGTH)} }' file
* apple *
* orange tasty *
* banana *

The above solve the problem with a couple of trivial regexps instead of one complicate regexp. It's trivial to remove the *s at each end of the output but I feel like the OP is going to want to use some convoluted single regexp instead.

Related