Skip to content

Conditionals, and the (?(DEFINE)…) block

One line: (?(1)then|else) runs one branch or the other depending on whether group 1 participated in the match — and Python's re has it, which is the thing about conditionals most people get wrong.

Level: 301 · you write patterns in more than one engine and are deciding whether this one is worth its reading cost

Keywords on this page: (?(1)…) · (?<name>…) · (?R) · (?=…) · (?#…) — Topics: Readable patterns

A conditional is the only construct in a regular expression that is an if. Everything else — alternation, quantifiers, lookaround — decides what to try; a conditional decides based on what already happened.

The shortest honest use

An optional opening delimiter that makes the closing one mandatory:

(<)?\w+(?(1)>)
 ^^^       ^^^^^^^
 |         if group 1 participated, a '>' is required here
 optional '<', group 1

Three subjects settle it. <tag> must match, tag must match, and <tag — opened and never closed — must not. That is not expressible by alternation without writing the body twice (<\w+>|\w+), and the body is usually not one \w+ but forty characters of pattern you do not want two copies of.

Those three subjects are the question every engine on this page is handed.

Python has conditionals

The assumption worth dismantling first: Python's re has no recursion, no \K, no variable-width lookbehind, no possessive quantifiers until 3.11… so surely no conditionals either. It has them. This library's own capability probe never asked — its eleven rows stop at recursion, and a "recursion: no" next to "recursion is Perl's alone" reads, wrongly, as a verdict on the whole Perl/PCRE end of the feature list — so the page you would naturally check does not tell you.

Verified output of conditional_branch_py.py — regenerated by tools/run_examples.py, never hand-typed.

                               <tag>  tag    <tag
numbered   (?(1)>)             yes    yes    no
named      (?(open)>)          yes    yes    no
PCRE named (?(<open>)>)        compile error
lookaround (?(?=<)<\w+>|\w+)   compile error
DEFINE     (?(DEFINE)...)      compile error
no such group (a)(?(2)x|y)     compile error

then someone adds an optional numeric prefix in front of it:
numbered   (?(1)>)             no     yes    yes
named      (?(open)>)          yes    yes    no

Read the top block as the feature working: yes yes no, exactly the three answers the construct exists to give, by number and by name.

Then read the refusals, because Python's conditional is one spelling, not four:

What Perl and PCRE2 write Python
(?(1)>) same — the only spelling both agree on
(?(<open>)>) error. Python has no (?<name>…) groups at all; the group is (?P<open>…) and the condition is the bare (?(open)…)
(?(?=<)yes|no) error. Python has no lookaround condition
(?(DEFINE)…) error — it reads DEFINE as a group name and cannot find it

So a conditional is the rare construct where a pattern moves from Python to PCRE unchanged and back again only if it was written by number. The named form does not survive the trip in either direction.

The bottom block is the trap, and it is the reason to use the named form anyway. See below.

Perl, and therefore PCRE2, and therefore ABAP

Perl has the whole family: by number, by name in angle brackets, and with a lookaround as the condition — (?(?=<)<\w+>|\w+) reads "if what follows is a <, match a tagged word, otherwise a bare one".

Verified output of conditional_branch_pl.pl — regenerated by tools/run_examples.py, never hand-typed.

                               <tag>  tag    <tag
numbered   (?(1)>)             yes    yes    no
named      (?(<open>)>)        yes    yes    no
named      (?(open)>)          compile error
lookaround (?(?=<)...|...)     yes    yes    no

(a)(?(2)x|y) with no group 2: compiled
(a)(?(2)x|y)       on ay  yes
(a)(?(2)x|y)       on ax  no

(a)(?(R)x|y)       on ay  yes
(a)(?(R)x|y)       on ax  no
(?<R>x)(?(R)y|n)   on xy  no
(?<R>x)(?(R)y|n)   on xn  yes
(?<R>x)(?(<R>)y|n) on xy  yes
(?<R>x)(?(<R>)y|n) on xn  no

ipv4 192.168.0.1      yes
ipv4 255.255.255.255  yes
ipv4 256.1.1.1        no
ipv4 1.2.3            no
ipv4 01.2.3.4         no
the DEFINE block alone, on the empty string: yes
the DEFINE block alone, on '7':              no

Four things in that output are worth stopping on.

Perl refuses (?(open)…). The bare name is Python's spelling; Perl wants (?(<open>)…) or (?('open')…). The two engines that both have named conditionals have no named spelling in common.

(?(2)…) with no group 2 compiles. Perl does not complain, and the condition is simply never true. Every other engine measured here — Python, Ruby, PCRE2, fancy-regex — calls that a compile error. Perl's tolerance is the dangerous one: a typo'd group number in Perl is a pattern that runs and quietly always takes the else.

R is not a group name — it is the recursion test — and in Perl it beats a group actually named R. (a)(?(R)x|y) compiles with no group named R in sight, and the condition is false because we are not inside a recursive call. Add the group and nothing changes: (?<R>x)(?(R)y|n) matches xn, not xy. Written (?(<R>)y|n) it asks about the group instead and matches xy. Two characters, opposite meanings, no warning.

The DEFINE block matches the empty string and nothing else. ^(?(DEFINE)(?<octet>\d+))$ matches "" and rejects "7" — proof that the block is a declaration, not a pattern.

PCRE2, measured

ABAP's PCRE addition is the PCRE2 library, so the way to find out what an ABAP FIND … OF PCRE will do is to ask PCRE2. Nothing in this repository can run ABAP; pcre2grep is the honest stand-in.

Real run — PCRE2 10.48 (pcre2grep), macOS 26.6.2 x86-64, 2026-09-22. Not a generated block: typed at a shell and pasted.

$ pcre2grep -V
pcre2grep version 10.48 2026-08-31
$ cat tags.txt
<tag>
tag
<tag
$ pcre2grep -n '^(<)?\w+(?(1)>)$' tags.txt
1:<tag>
2:tag
$ pcre2grep -n '^(?<open><)?\w+(?(<open>)>)$' tags.txt
1:<tag>
2:tag
$ pcre2grep -n '^(a)(?(2)x|y)$' tags.txt
pcre2grep: Error in command-line regex at offset 6: reference to non-existent subpattern
$ cat a.txt
ay
ax
$ pcre2grep -n '^(a)(?(R)x|y)$' a.txt
1:ay
$ pcre2grep -n '^(a)(?(R1)x|y)$' a.txt
1:ay
$ cat r.txt
xy
xn
$ pcre2grep -n '^(?<R>x)(?(R)y|n)$' r.txt
1:xy
$ cat hosts.txt
192.168.0.1
255.255.255.255
256.1.1.1
1.2.3
$ pcre2grep -n "(?x) (?(DEFINE) (?<octet> 25[0-5] | 2[0-4]\d | 1\d\d | [1-9]?\d ) )
           ^ (?&octet) \. (?&octet) \. (?&octet) \. (?&octet) $" hosts.txt
1:192.168.0.1
2:255.255.255.255

Two differences from Perl, in one transcript. PCRE2 rejects the condition on a non-existent group where Perl accepts it. And PCRE2 gives a real group named R priority over the recursion test — (?(R)y|n) matched xy here and xn in Perl, from the same pattern text — while agreeing with Perl that (?(R)…) and (?(R1)…) are recursion tests when no group is named that. PCRE2 also accepts the bare (?(open)…) that Perl refuses, so of the four engines with named conditionals it is the only one that takes both spellings.

If the pattern is going into ABAP, write the condition as (?(<name>)…): it is the spelling that means the same thing in Perl, in PCRE2 and in Ruby, and the one that cannot be mistaken for R.

z_conditional.prog.abap in full — pasted here by tools/run_examples.py from the file CI runs.

REPORT z_conditional.

" ABAP's PCRE addition is the PCRE2 library in the kernel, so ABAP has the
" whole conditional family: (?(1)yes|no), (?(<name>)yes|no), a lookaround as
" the condition, and the (?(DEFINE)...) block.
" Source only -- nothing in this repository can run ABAP. Checked by abaplint.

TYPES ty_subjects TYPE STANDARD TABLE OF string WITH EMPTY KEY.

" An optional opening delimiter that makes the closing one mandatory:
" <tag> and tag match, <tag does not.
"
" Named, not numbered. (?(1)...) would be correct today and wrong the moment
" somebody adds a capture group in front of it, and nothing would report it.
DATA(pattern) = `^(?<open><)?\w+(?(<open>)>)$`.

DATA(subjects) = VALUE ty_subjects( ( `<tag>` ) ( `tag` ) ( `<tag` ) ).

LOOP AT subjects INTO DATA(subject).
  FIND FIRST OCCURRENCE OF PCRE pattern IN subject.
  IF sy-subrc = 0.
    WRITE / |{ subject } matches|.
  ELSE.
    WRITE / |{ subject } does not match|.
  ENDIF.
ENDLOOP.

" (?(DEFINE)...) declares named parts and matches nothing itself, so the
" pattern proper can be assembled out of them. With (?x) -- extended, or
" free-spacing, mode -- whitespace inside the pattern is ignored and the
" definition fits on a line a reviewer can check.
DATA(ipv4) =
  `(?x)`                                                 &&
  `(?(DEFINE)`                                           &&
  `  (?<octet>  25[0-5] | 2[0-4]\d | 1\d\d | [1-9]?\d )`  &&
  `)`                                                    &&
  `^ (?&octet) \. (?&octet) \. (?&octet) \. (?&octet) $`.

DATA(host) = `192.168.0.1`.
FIND FIRST OCCURRENCE OF PCRE ipv4 IN host.
IF sy-subrc = 0.
  WRITE / |{ host } is a dotted quad|.
ENDIF.

" A condition naming a group the pattern does not have is a compile error in
" PCRE2 -- CX_SY_INVALID_REGEX at runtime here, not a silent false branch the
" way Perl takes it. And (?(R)...) is PCRE2's recursion test, so a group you
" name R has to be asked for as (?(<R>)...).

Ruby has them too — in exactly one spelling

Onigmo is usually described as "Perl-like, missing a different corner from Java". Conditionals are a corner it did not miss, which surprises people who know Ruby has no (?(DEFINE)…).

Verified output of conditional_branch_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

                               <tag>  tag    <tag
numbered   (?(1)>)             yes    yes    no
named      (?(<open>)>)        yes    yes    no
named      (?(open)>)          compile error
lookaround (?(?=<)...|...)     compile error
DEFINE     (?(DEFINE)...)      compile error

(a)(?(2)x|y) with no group 2: compile error
(?<R>x)(?(R)y|n):             compile error
(?<R>x)(?(<R>)y|n) on xy  yes
(?<R>x)(?(<R>)y|n) on xn  no

Ruby takes (?(1)…) and (?(<open>)…) and refuses everything else in the family: no bare name, no lookaround condition, no DEFINE — even though Ruby does have the subroutine calls \g<name> that a DEFINE block would feed, which are the subject of matching nested structures. And Ruby has no recursion condition at all, so (?(R)…) is a compile error rather than a different meaning: the one engine here where a group named R cannot bite you.

Rust: regex no, fancy-regex yes — with a silent wrong answer

Verified output of conditional_branch_rs_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

regex:       refused
regex parse error:
    ^(<)?\w+(?(1)>)$
              ^
error: unrecognized flag

fancy-regex                    <tag>  tag    <tag
numbered   (?(1)>)             yes    yes    no
named      (?(<open>)>)        yes    yes    no
named      (?(open)>)          no     yes    yes
named      (?(zz)>)            no     yes    yes
named      (?(<zz>)>)          refused
lookaround (?(?=<)...|...)     refused
DEFINE     (?(DEFINE)...)      refused

(a)(?(2)x|y) with no group 2: refused

regex has no group references of any kind, so there is nowhere to hang a condition; it reads (?( as an inline flag group and says unrecognized flag.

fancy-regex does implement conditionals, numbered and (?(<name>)…), and gets all three subjects right. That is worth knowing if you are porting Perl patterns to Rust, and it is not what the crate's reputation as "regex plus backreferences and lookahead" would lead you to expect.

Then look at the third row. (?(open)…) — Python's spelling, handed to fancy-regex — compiles, and answers no yes yes: the exact inverse of the correct yes yes no. It is treating the condition as permanently false. The two rows under it show how far that goes: (?(zz)…), naming a group the pattern does not contain at all, compiles to the same permanently-false condition, while the angle-bracket form of the identical missing name is checked and refused.

Perl, Ruby and Python each reject the named spelling they do not use. fancy-regex accepts it and means something else — the worst of the four outcomes, and the one no test suite catches unless it asserts on <tag.

The four engines that have nothing

Verified output of conditional_absent_sh.sh — regenerated by tools/run_examples.py, never hand-typed.

javascript   refused at compile time
java         refused at compile time
go regexp    refused at compile time

Java's java.util.regex, JavaScript's RegExp and Go's regexp all refuse the pattern outright, as does Rust's regex above. For Go that is the RE2 guarantee doing its job — a conditional is a question about match history, and a finite automaton does not have one. For Java and JavaScript it is simply a feature never added; both have the backreferences a conditional would test.

Their replacement is one line of host language: if (m.group(1) != null).

(?(DEFINE)…): the closest a regex comes to a function definition

A DEFINE block is a conditional whose condition is never true, so its contents never run — but the named groups inside it are defined, and the rest of the pattern can call them. The block contributes nothing to the match. It is a declarations section.

Combined with free-spacing mode (/x, or (?x) inline), it turns a pattern into something a reviewer can approve line by line. The Perl example above builds a dotted quad this way:

my $ipv4 = qr{
    (?(DEFINE)
        (?<octet>  25[0-5] | 2[0-4]\d | 1\d\d | [1-9]?\d )
    )
    ^ (?&octet) \. (?&octet) \. (?&octet) \. (?&octet) $
}x;

octet is written once and stated four times. The alternative is the same forty characters copied four times, where the reviewer's real job is to diff four near-identical blobs and where a fix lands in three of them.

Two things it is not:

  • It is not recursion. (?&octet) here calls a subpattern that does not call itself. The same construct used recursively is matching nested structures, which is a different lesson and a different argument.
  • It is not portable. Perl and PCRE2 only — so ABAP yes, Python no, Ruby no, Java no, JavaScript no, Go no, Rust no. Everywhere else the same job is done by building the pattern string from named pieces in the host language, which reads about as well and works in all nine.

The trap: the number moves

The bottom block of the Python output is one edit. Somebody adds an optional numeric prefix in front of an existing pattern — a wholly reasonable change, in a group of its own because they want the number:

before   ^(<)?\w+(?(1)>)$
after    ^(?:(\d+):)?(<)?\w+(?(1)>)$

Group 1 is now the prefix. The conditional still compiles, still reads the same, and every answer inverts:

subject before after
<tag> yes no
tag yes yes
<tag no yes

The pattern now accepts the one string it was written to reject, and rejects the one it was written to accept. No engine warns, because nothing is wrong: group 1 exists and the condition is well-formed.

The named form on the row below it survives the identical edit untouched. Write conditionals by name. This is the same argument as in forward references, and it is stronger here, because a backreference by the wrong number usually fails loudly against real input while a conditional by the wrong number just changes which branch runs.

Two footnotes on "a condition naming a group that is not there":

  • Only Perl accepts it silently. PCRE2, Python, Ruby and fancy-regex all refuse to compile.
  • But R is not a group name in Perl or PCRE2 — it is the recursion test — so (?(R)…) is accepted by both even when no group is named R, with the condition simply answering "no" outside a recursive call. (?(R1)…) likewise. That is the real form of the folk claim that a missing group becomes a recursion test: it is not any missing group, it is the reserved letter, and the two engines resolve the collision with a genuine group in opposite directions.

The measured summary

Every cell is a program in examples/ compiling that spelling, except the PCRE2 row, which is the pcre2grep transcript above.

Engine (?(1)…) (?(<name>)…) (?(name)…) (?(?=…)…) (?(DEFINE)…) missing group
Perl 5.42 yes yes no yes yes compiles, never true
PCRE2 10.48 / ABAP PCRE yes yes yes yes yes compile error
Python 3.14 re yes no yes no no compile error
Ruby 4.0 (Onigmo) yes yes no no no compile error
Rust fancy-regex 0.16 yes yes compiles, always false no no compile error
Java 25 no no no no no —
JavaScript (node 20) no no no no no —
Go 1.25 regexp no no no no no —
Rust regex 1.13 no no no no no —

The one row that is not a "does it compile" answer is fancy-regex under (?(name)…), and it is the only cell on the page where an engine accepts a pattern and means something the author did not write.

Those versions are the ones on the machine that recorded the answer keys. CI re-runs every program above on ubuntu-latest with Python 3.12, Perl 5.38, Ruby 3.4, a newer node and the same Go 1.25 and Java 25, against these same keys — so a release that disagreed with a cell would fail the build rather than quietly age the page.

So should you use one?

Most of the time a conditional is a sign that the problem wanted two patterns and an if in the host language. <tag> or tag is two patterns; the reason to fuse them is that the body between the delimiters is long, and duplicating it is worse than the conditional. That is a real reason, and it is the only one that survives contact with a reviewer.

Reach for it when:

  • The scan is single-pass over a large input and you cannot afford to try a second pattern on every line. A conditional does in one traversal what two match calls do in two.
  • The pattern has to live somewhere there is no host language — a config file, a log-router rule, a database check constraint, a pcre2grep invocation in a script, an ABAP field validation stored as data. This is the strongest case, and it is where the DEFINE block pays for itself too, because the config file is exactly where nobody can refactor the duplication out for you.

Do not reach for it when the body is short enough to write twice, when the pattern is going to be read by people who have not met the construct, or when the same logic would be three lines of host code that can carry a comment and a test. And never reach for it by number in a pattern anyone else will edit.

See also