How to match a string between two boundaries when only one or none may be present using a regex

Viewed 144

I want to match for any string of characters between two words ("Hello" and "Goodbye" in the following examples) using a regex.

The bolded areas in the following list should match:

  • Hello, I like you. Goodbye.
  • Hello there, do you enjoy golf?
  • I like you. Goodbye. See you later.

Examples of strings that should not match at all include (basically I want to treat the words "Hello" and "Goodbye" as a kind of barrier):

  • HelloGoodbye
  • Goodbye, how are you?
  • How are you? Hello

I tried using (?<=Hello).*(?=Goodbye), which works in some cases (see here). The issue with this regex is that if for example "Goodbye" isn't present, none of the text after "Hello" matches (and vice versa).

I'm not exactly sure that the regex I have tried is a good way to go about it. Possibly, I just need to match any part of the string that follows "Hello" and/or preceeds "Goodbye" (but neither need to be present for a match).

I believe I need to have some kind of conditional, and I guess matching the first two is easy but I am unable to find a way to do it.

Any help would be appreciated as I am still new to using regular expressions.

2 Answers

Use

(?<=Hello|^)(?:(?!Hello|Goodbye).)+(?=Goodbye|$)

See proof

EXPLANATION

                         EXPLANATION
--------------------------------------------------------------------------------
  (?<=                     look behind to see if there is:
--------------------------------------------------------------------------------
    Hello                    'Hello'
--------------------------------------------------------------------------------
   |                        OR
--------------------------------------------------------------------------------
    ^                        the beginning of the string
--------------------------------------------------------------------------------
  )                        end of look-behind
--------------------------------------------------------------------------------
  (?:                      group, but do not capture (1 or more times
                           (matching the most amount possible)):
--------------------------------------------------------------------------------
    (?!                      look ahead to see if there is not:
--------------------------------------------------------------------------------
      Hello                    'Hello'
--------------------------------------------------------------------------------
     |                        OR
--------------------------------------------------------------------------------
      Goodbye                  'Goodbye'
--------------------------------------------------------------------------------
    )                        end of look-ahead
--------------------------------------------------------------------------------
    .                        any character except \n
--------------------------------------------------------------------------------
  )+                       end of grouping
--------------------------------------------------------------------------------
  (?=                      look ahead to see if there is:
--------------------------------------------------------------------------------
    Goodbye                  'Goodbye'
--------------------------------------------------------------------------------
   |                        OR
--------------------------------------------------------------------------------
    $                        before an optional \n, and the end of
                             the string
--------------------------------------------------------------------------------
  )                        end of look-ahead

As an alternative, not quite as sophisticated as the accepted answer and matching differently in case of repetitive boundary words "Hello" and "Goodbye", but maybe a bit easier to understand because it just uses a lazy/reluctant quantifier *? for the match and does not resort to look-behind or look-ahead:

^(?:.*Hello)?(.*?)(?:Goodbye.*)?$

The non-capturing groups starting with (?: make sure that group 1 matches what you need. If you do not mind using group 2, you do not need to use non-capturing groups at all. Keep it simple! Then the regex would read:

^(.*Hello)?(.*?)(Goodbye.*)?$

You can test the first regex here.

See also this regex quantifier cheat sheet.

Related