TechEarl

The Regex (*ACCEPT) Control Verb, Explained

What the PCRE (*ACCEPT) backtracking control verb does, how it forces an immediate successful match, how it behaves inside capturing groups, which engines support it, and where it is genuinely useful.

Ishan KarunaratneIshan Karunaratne⏱️ 8 min readUpdated
The PCRE (*ACCEPT) backtracking control verb: how it forces an immediate successful regex match, how capturing groups are closed when it fires, which engines support it, and the backtracking control verb family.

(*ACCEPT) is a regex backtracking control verb. When the matching engine reaches it, the match succeeds immediately. Everything in the pattern after (*ACCEPT) is never processed: the engine stops, declares the match a success, and returns whatever has been consumed so far.

It is a niche feature, but a genuinely useful one once you understand it. This article covers exactly what (*ACCEPT) does, how it behaves inside capturing groups, which engines support it (the short answer: PCRE, and not much else), and the situations where reaching for it actually makes a pattern simpler.

What (*ACCEPT) does

A normal regex succeeds only when the engine runs off the end of the pattern with everything matched. (*ACCEPT) short-circuits that. It is an early exit.

Take this pattern:

code
foo(*ACCEPT)bar

Against the input foozzz, a normal foobar pattern would fail (no bar). But foo(*ACCEPT)bar matches foo and stops. The engine consumes foo, hits (*ACCEPT), and reports success right there. The bar after the verb is dead pattern: it is never even looked at.

That is the whole idea. (*ACCEPT) means "stop here, the match is good, do not process the rest."

The syntax

The verb is the literal text (*ACCEPT), written inline anywhere in the pattern. It takes no quantifier and matches no characters of its own (it is zero-width). In PCRE2 it can optionally carry a name, (*ACCEPT:NAME), which is passed back to the caller the same way a (*MARK:NAME) is. The name is informational; it does not change the matching behavior.

(*ACCEPT) is one of PCRE's backtracking control verbs, a family that also includes (*FAIL), (*SKIP), (*PRUNE), (*COMMIT), (*THEN), and (*MARK). They are covered briefly at the end of this article.

How it behaves inside capturing groups

This is the part worth understanding properly, because it is what makes (*ACCEPT) useful rather than just a curiosity.

When (*ACCEPT) is reached inside one or more capturing groups, those groups are closed as if they had matched normally, capturing whatever they had consumed up to that point. The capture is not discarded just because the group's closing parenthesis was never reached.

code
foo(bar(*ACCEPT)baz)qux

Against foobarXXX:

  • The engine matches foo, enters group 1, matches bar, and hits (*ACCEPT).
  • The overall match succeeds and equals foobar.
  • Group 1 is set to bar, even though the engine never reached the group's ).
  • baz and qux are never processed.

So (*ACCEPT) does not throw away your captures. It commits whatever has been captured so far and exits cleanly. That is what lets you use it as a deliberate "the answer is good, take it now" signal in the middle of a complex pattern.

Where (*ACCEPT) is actually useful

The honest answer is that most patterns never need it. But there are a few recurring situations where it earns its place.

Early exit from one branch of an alternation

When a pattern has several alternative branches and matching any one of them means the whole match is complete, (*ACCEPT) lets a branch declare victory without you having to factor the "rest of the pattern" out of every branch.

code
^(?:cache-hit(*ACCEPT)|cache-miss|cache-stale)\s+\d+ms$

Here a line of cache-hit is considered a complete, valid match on its own. The other two branches still require the \s+\d+ms$ timing suffix. Without (*ACCEPT) you would either duplicate the suffix or restructure the alternation. The verb keeps the special-case branch self-contained.

Stopping a pattern once you have matched "enough"

If you only need to confirm that input starts a certain way and you do not care what follows, (*ACCEPT) ends the pattern as soon as the prefix is confirmed.

code
^https?://[^/\s]+(*ACCEPT)

This succeeds as soon as a scheme and host are present. The path, query, and fragment are irrelevant to the question being asked, so the pattern simply does not describe them.

Recursion and DEFINE blocks

In recursive patterns and (?(DEFINE)...) subroutine definitions, (*ACCEPT) is a common way to terminate a recursive descent once a base case is satisfied. That is an advanced use, but it is the place where (*ACCEPT) stops being optional and becomes the natural tool.

A caveat on the flip side: do not reach for (*ACCEPT) when an ordinary pattern would do. A clear foo is better than a clever foo(*ACCEPT). The verb is for the cases where the alternative is genuinely more tangled, not for showing off.

(*ACCEPT) is not the "exclude" idiom

People often arrive at (*ACCEPT) looking for the famous "match X but not Y" trick. That trick does not use (*ACCEPT). It uses (*SKIP)(*FAIL):

code
Y-to-ignore(*SKIP)(*FAIL)|X-to-keep

The pattern matches the thing you want to ignore, then (*SKIP)(*FAIL) deliberately fails it and tells the engine to jump past it, so the X-to-keep branch only ever sees the input you care about.

(*ACCEPT) is the opposite polarity: it forces success, not failure. Keep the two straight. (*ACCEPT) says "this is a match, stop." (*FAIL) says "this is not a match, backtrack."

Engine support

This is the catch. (*ACCEPT) is a PCRE feature, and it is not portable.

Engine / language(*ACCEPT) supported
PCRE and PCRE2Yes
PHP (preg_match, preg_replace, etc.)Yes (PHP uses PCRE)
Perl 5.10+Yes
R (with perl = TRUE)Yes
Python, third-party regex moduleYes
Python built-in re moduleNo
JavaScript (RegExp)No
Java (java.util.regex)No
.NET (System.Text.RegularExpressions)No
Go (regexp, RE2)No

If your code runs on JavaScript, Python's standard re, Java, .NET, or Go, (*ACCEPT) is not available and will be a syntax error or a literal-text mismatch. In those environments you restructure the pattern, or you split the logic into two steps in the host language. Before using it, confirm your stack is on PCRE. The most common place it is safe is PHP.

The backtracking control verb family

(*ACCEPT) is one of seven. Here is the whole set, from the PCRE2 documentation, so you can see where it sits:

VerbEffect
(*ACCEPT)Force an immediate successful match.
(*FAIL) or (*F)Force a failure, triggering backtracking.
(*MARK:NAME)Set a named marker, passed back to the caller after the match.
(*COMMIT)On backtracking, fail the whole match with no further start positions tried.
(*PRUNE)On backtracking, fail and advance the start to the next character.
(*SKIP)On backtracking, fail and advance the start to the current matching position.
(*THEN)On backtracking, fail locally and move to the next alternative.

(*ACCEPT) and (*FAIL) act immediately when reached. The other backtracking-triggered verbs only do something when the engine backtracks onto them. All of them can carry an optional :NAME; (*MARK) is the one that requires it.

Common mistakes

Expecting it to work in JavaScript or Python re. This is the big one. (*ACCEPT) is PCRE. A JavaScript RegExp treats (*ACCEPT) as an error or as literal characters, not as a control verb. Check the engine first.

Thinking it discards captures. It does not. Capturing groups open at the point (*ACCEPT) fires are closed and keep their captured text. If you wanted those captures empty, (*ACCEPT) is the wrong tool.

Using it where a plain pattern is clearer. (*ACCEPT) is an early exit, and early exits make patterns harder to read. Use it when it genuinely simplifies an alternation or a recursion, not by reflex.

Confusing it with (*SKIP)(*FAIL). (*ACCEPT) forces success. The exclude-this-text idiom forces failure. They are not interchangeable.

FAQ

See also

External references: the PCRE2 pattern documentation is the authoritative source for the backtracking control verbs, with the syntax summary in pcre2syntax. Test patterns at regex101.com with the PCRE2 flavor selected, which is the easiest way to watch (*ACCEPT) short-circuit a match in the debugger.

TagsRegular ExpressionsRegexPCREControl VerbsPattern MatchingBacktrackingPHP
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

grep -E vs grep -P explained: basic regex (BRE) treats + ? | ( ) { } as literal text, extended regex (ERE) makes them metacharacters, and PCRE adds lookaround and \d. Plus why macOS BSD grep has no -P.

grep Regex: BRE vs ERE vs PCRE Explained

grep has three regex engines and the default one surprises everyone: in basic regex (BRE) the characters + ? | ( ) { } are literal text until you backslash-escape them. -E switches to extended regex (ERE) where they work bare, and -P unlocks Perl-compatible regex with lookaround and \d. The full BRE vs ERE vs PCRE comparison, the same pattern in all three, and why -P does not exist on macOS.

find -name uses shell globs on the basename; find -regex matches a full regular expression against the whole path. The -regextype flavors, the GNU emacs vs BSD basic default drift, and when each one is the right tool.

find -regex vs -name: When to Use Regex in find

find -name takes a shell glob and matches the basename; find -regex takes a full regular expression and matches the whole path. That whole-path detail is the number one surprise: -regex '.*\.txt' works but -regex '.txt' matches nothing. The flag reference, -regextype flavors, the GNU vs BSD default-flavor drift, and when -name is the better tool.

Exclude a directory in find with -path './node_modules' -prune -o ... -print. Why the trailing -print is mandatory, the multi-directory form, the slower -not -path alternative, and BSD vs GNU notes.

How to Exclude a Directory in find (the -prune Pattern Explained)

find -path './node_modules' -prune -o -type f -print skips a directory subtree instead of walking into it. The pattern looks strange because -prune is an action, not a test, and the trailing -print is mandatory once you write an explicit action. The breakdown, the multi-directory form, the slower -not -path alternative, and when each one is the right call.