QUANTIFIERS
Greedy vs lazy
Greedy quantifiers grab as much as possible; add ? to make them grab as little.
By default every quantifier is greedy: it grabs as much as it possibly can. Try matching the quoted parts in say "hi" and "bye" with ".*". You get ONE match, "hi" and "bye", because the greedy .* ran all the way to the LAST quote in the line.
Adding ? after a quantifier makes it lazy: now it grabs as LITTLE as possible. ".*?" stops at the first closing quote, giving you "hi" and "bye" as separate matches.
The clearest fix is usually neither: describe the content precisely with a negated set. "[^"]*" means a quote, then any number of characters that are NOT quotes, then a quote. It cannot overrun because [^"] can never eat the closing quote.
Greediness has a dark side. A quantifier nested inside another quantifier, like (.*)* or (a+)+, can force the engine to try an exponential number of paths before it gives up. On the wrong input that hangs the program - a denial-of-service bug called catastrophic backtracking (ReDoS). Some flavors offer atomic groups or possessive quantifiers to defuse it, but the portable cure is the precise, non-overlapping patterns shown above. OWASP: ReDoS.
Match each quoted string separately, quotes included.
say "hi" and "bye" now
a "single" one
no quotes here
Match each HTML tag separately, angle brackets included.
<b>hi</b>
a <a href="x">link</a>
no tags here