Flags, and where you can turn one on¶
One line: Eight engines spell case-insensitivity i and then part company — m means multiline in Perl, PCRE2, Python, Java, JavaScript, Go and Rust, and dot-matches-newline in Ruby — so put the flag inside the pattern as (?i), (?s), (?x), where it travels with the pattern and can be scoped to (?i:…) when only part of it needs the change.
Level: 301 · you move patterns between languages, or keep them somewhere a flags argument cannot follow
Keywords on this page: (?i) · (?:…) · . · ^ · $ · (?#…) — Topics: Multiline text and logs · Readable patterns · Portability between engines
A flag is the smallest piece of a regular expression and the one most likely to be written from memory. It has two spellings everywhere: an argument next to the pattern (re.MULTILINE, Pattern.DOTALL, /m) and a modifier inside it ((?m), (?s)). The second is the one worth the habit, and the rest of this page is the measurement of why.
The letter that costs a day¶
Here is the same idea in two engines. Both are given one.two over a subject with a line break in the middle, and both are given the flag letter m.
Python¶
Verified output of inline_flags_py.py — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
one.two no match
one.two re.DOTALL match "one\ntwo"
(?s)one.two match "one\ntwo"
subject "one\rtwo"
one.two match "one\rtwo"
== the letter m: line anchors ==
subject "one\ntwo\nthree"
^two$ no match
^two$ re.MULTILINE match "two"
(?m)^two$ match "two"
one.two re.MULTILINE no match
subject "abc\r\ndef"
^abc$ re.MULTILINE no match
^abc\r?$ re.MULTILINE match "abc\r"
== free-spacing: re.VERBOSE ==
one line match "2026-09-22"
re.VERBOSE match "2026-09-22"
a b on "a b" no match
a b on "ab" match "ab"
a\ b on "a b" match "a b"
[ ] on "a b" match "a b"
a#b on "a#b" match "a"
== inline and scoped modifiers ==
abc on ABC no match
abc re.I on ABC match "ABC"
(?i)abc on ABC match "ABC"
(?i:b)c on Bc match "Bc"
(?i:b)c on bC no match
(?i)ab(?-i:cd) on ABcd match "ABcd"
(?i)ab(?-i:cd) on ABCD no match
a(?i)bc on aBC compile error
(a(?i)b)c on aBc compile error
(a(?i)b)c on abC compile error
Ruby¶
Verified output of inline_flags_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
/one.two/ no match
/one.two/m match "one\ntwo"
/(?m)one.two/ match "one\ntwo"
"(?s)one.two" compile error
subject "one\rtwo"
/one.two/ match "one\rtwo"
== the letter m is NOT multiline here ==
subject "one\ntwo\nthree"
/^two$/ no flag match "two"
/^two$/m match "two"
subject "abc\r\ndef"
/^abc$/ no match
/^abc\r?$/ match "abc\r"
== free-spacing: /x ==
one line match "2026-09-22"
/x match "2026-09-22"
/a b/x on "a b" no match
/a b/x on "ab" match "ab"
/a\ b/x on "a b" match "a b"
/a#b/x on "a#b" match "a"
== inline and scoped modifiers ==
/abc/ on ABC no match
/abc/i on ABC match "ABC"
/(?i)abc/ on ABC match "ABC"
/(?i:b)c/ on Bc match "Bc"
/(?i:b)c/ on bC no match
/(?i)ab(?-i:cd)/ on ABcd match "ABcd"
/(?i)ab(?-i:cd)/ on ABCD no match
/a(?i)bc/ on aBC match "aBC"
/a(?i)bc/ on ABC no match
/(a(?i)b)c/ on aBc match "aBc"
/(a(?i)b)c/ on abC no match
Read the first block of each. Python's re.MULTILINE leaves one.two unmatched — the letter changed the anchors, not the dot. Ruby's /m matches "one\ntwo" — the letter changed the dot, not the anchors. The same letter, the same pattern, the same subject, two different answers, and neither engine reports anything wrong.
The rest of the collision:
| Letter | Perl, PCRE2, Python, Java, JavaScript, Go, Rust | Ruby |
|---|---|---|
m |
^ and $ become line anchors |
. matches a newline |
s |
. matches a newline |
no such letter — (?s) is a compile error |
Ruby's two rows are not an oversight. Ruby's ^ and $ are line anchors already, with no flag — the /^two$/ no flag line matches "two" inside the three-line subject — so there was never a multiline flag to name. If you want "the very beginning and end of the string" in Ruby, the answer is \A and \z, which is $ and the end of the string.
The practical shape of this trap: a pattern written in Ruby, copied into Python with its m, silently stops matching across line breaks; the same pattern copied the other way silently starts.
What the dot excludes, and what it does not¶
. excludes the newline. That is the whole rule in Perl, PCRE2, Python, Ruby, Go and Rust — \n and nothing else, so . happily matches a carriage return. Java and JavaScript exclude every line terminator instead, \r among them.
That difference is invisible until a file written on Windows reaches a pattern tested on a file written anywhere else.
Java¶
Verified output of inline_flags_java.java — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
one.two no match
one.two DOTALL match "one\ntwo"
(?s)one.two match "one\ntwo"
subject "one\rtwo"
one.two no match
one.two UNIX_LINES match "one\rtwo"
== the letter m: line anchors ==
subject "one\ntwo\nthree"
^two$ no match
^two$ MULTILINE match "two"
(?m)^two$ match "two"
one.two MULTILINE no match
subject "abc\r\ndef"
^abc$ (?m) match "abc"
^abc$ (?m)(?d) no match
== free-spacing: COMMENTS / (?x) ==
one line match "2026-09-22"
(?x) match "2026-09-22"
a b (?x) on "a b" no match
a b (?x) on "ab" match "ab"
a\ b (?x) on "a b" match "a b"
a#b (?x) on "a#b" match "a"
== inline and scoped modifiers ==
abc on ABC no match
abc CASE_INSENSITIVE match "ABC"
(?i)abc on ABC match "ABC"
(?i:b)c on Bc match "Bc"
(?i:b)c on bC no match
(?i)ab(?-i:cd) on ABcd match "ABcd"
(?i)ab(?-i:cd) on ABCD no match
a(?i)bc on aBC match "aBC"
a(?i)bc on ABC no match
(a(?i)b)c on aBc match "aBc"
(a(?i)b)c on abC no match
JavaScript¶
Verified output of inline_flags_js.js — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
/one.two/ no match
/one.two/s match "one\ntwo"
subject "one\rtwo"
/one.two/ no match
/one.two/s match "one\rtwo"
== the letter m: line anchors ==
subject "one\ntwo\nthree"
/^two$/ no match
/^two$/m match "two"
/one.two/m no match
subject "abc\r\ndef"
/^abc$/m match "abc"
/^\w+$/m match "abc"
== the letter i, the one uncontested flag ==
/abc/ on ABC no match
/abc/i on ABC match "ABC"
== no free-spacing mode ==
one line match "2026-09-22"
/a b/ on "a b" match "a b"
/a#b/ on "a#b" match "a#b"
Both blocks answer no match to one.two over "one\rtwo", where Python, Perl, Ruby, Go and Rust all answer match "one\rtwo". And the same split runs through the anchors, in the opposite direction from the one people expect:
| Engine | . matches \r? |
line anchors match abc in "abc\r\ndef"? |
|---|---|---|
| Perl | yes | no |
Python re |
yes | no |
| Ruby | yes | no |
Go regexp |
yes | no |
Rust regex |
yes | no — unless (?R) |
| Java | no | yes |
| JavaScript | no | yes |
The five "no" rows are one trap with one cause: to those engines the line break is \n, so in "abc\r\ndef" the position after abc is not the end of a line — there is still a \r to go. ^\w+$ fails on every line of a CRLF file, and the diagnosis "the regex is wrong" is the wrong diagnosis. Python, Perl, Ruby and Go each show the same repair on the next line of their block — ^abc\r?$, which matches "abc\r". Note that the \r is inside the reported match, which is its own small surprise when the captured text goes into a database column.
Two engines can be told instead of patched. Rust has a CRLF mode, (?R), which makes ^ and $ accept \r\n and takes \r out of the dot at the same time — both lines are in the Rust block below. Java arrives already knowing, and (?d) (UNIX_LINES) is the flag that takes the knowledge away: (?m)(?d)^abc$ is the one line in the Java block that stops matching.
PCRE2's newline convention is a setting rather than a rule, which makes it the odd one out; the measurement is in the real run further down.
Free-spacing: the flag that makes a pattern reviewable¶
x — re.VERBOSE in Python, COMMENTS in Java — ignores whitespace in the pattern and treats # as a comment to end of line. It is the only flag on this page that changes nothing about matching and everything about whether a colleague can read the thing.
Perl¶
Verified output of inline_flags_pl.pl — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
one.two no match
one.two /s match "one\ntwo"
(?s)one.two match "one\ntwo"
subject "one\rtwo"
one.two match "one\rtwo"
== the letter m: line anchors ==
subject "one\ntwo\nthree"
^two$ no match
^two$ /m match "two"
(?m)^two$ match "two"
one.two /m no match
subject "abc\r\ndef"
^abc$ /m no match
^abc\r?$ /m match "abc\r"
== free-spacing: /x and /xx ==
one line match "2026-09-22"
/x match "2026-09-22"
a b /x on "a b" no match
a b /x on "ab" match "ab"
a\ b /x on "a b" match "a b"
a#b /x on "a#b" match "a"
[a b]b /x on "a b" match " b"
[a b]b /xx on "a b" no match
== inline and scoped modifiers ==
abc on ABC no match
abc /i on ABC match "ABC"
(?i)abc on ABC match "ABC"
(?i:b)c on Bc match "Bc"
(?i:b)c on bC no match
(?i)ab(?-i:cd) on ABcd match "ABcd"
(?i)ab(?-i:cd) on ABCD no match
a(?i)bc on aBC match "aBC"
a(?i)bc on ABC no match
(a(?i)b)c on aBc match "aBc"
(a(?i)b)c on abC no match
The one line and /x lines match the same text, "2026-09-22". One of the two patterns is this:
and the other is this:
Every engine here that has x at all produces the same answer for both spellings, which is the point: free-spacing mode is free.
It has two traps, and both appear in every block above from an engine that has the flag.
A literal space stops being a literal space. a b under /x does not match "a b" — it matches "ab". Write a\ b or a[ ]b instead: the escape is measured in the Perl, Python, Ruby, Java and Rust blocks, and the class in the Python one. Python keeps whitespace inside a character class even in verbose mode ([ ] matches "a b" in the Python block); Perl's plain /x agrees, and Perl's /xx — Perl's alone — strips whitespace inside classes too, which is the [a b]b pair at the end of the free-spacing section: " b" under /x, no match under /xx.
A # starts a comment. a#b under free-spacing is just a, and it matches "a" inside "a#b" — in Perl, Python, Ruby, Java and Rust alike. If the # was meant literally, it needs \# or [#]. A pattern containing a URL fragment or a colour literal is the usual casualty.
Two engines have no free-spacing mode at all. Go refuses (?x) outright — compile error in the Go block — and JavaScript has no x flag as of ES2018, which is the one claim on this page that no program here prints; see the note at the end of the table.
Inline, scoped, and where a (?i) stops¶
The forms, in increasing order of precision:
| Form | Reach |
|---|---|
(?i) at the start of the pattern |
the whole pattern |
(?i) in the middle |
from there to the end of the enclosing group — not the end of the pattern |
(?i:…) |
exactly the sub-pattern in the brackets |
(?-i:…) |
exactly the sub-pattern in the brackets, with the flag turned back off |
The middle row is the one that is rarely documented and easy to get wrong, so it is measured everywhere. The probe is (a(?i)b)c against "aBc" and "abC": if the (?i) ends with the group, the c outside stays case-sensitive and "abC" must fail.
Go¶
Verified output of inline_flags_go.go — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
one.two no match
(?s)one.two match "one\ntwo"
subject "one\rtwo"
one.two match "one\rtwo"
== the letter m: line anchors ==
subject "one\ntwo\nthree"
^two$ no match
(?m)^two$ match "two"
(?m)one.two no match
subject "abc\r\ndef"
(?m)^abc$ no match
(?m)^abc\r?$ match "abc\r"
== no free-spacing mode ==
one line match "2026-09-22"
(?x)a b compile error
a b on "a b" match "a b"
== the letter U, Go and Rust only ==
a+ on "aaa" match "aaa"
(?U)a+ on "aaa" match "a"
== inline and scoped modifiers ==
abc on ABC no match
(?i)abc on ABC match "ABC"
(?i:b)c on Bc match "Bc"
(?i:b)c on bC no match
(?i)ab(?-i:cd) on ABcd match "ABcd"
(?i)ab(?-i:cd) on ABCD no match
a(?i)bc on aBC match "aBC"
a(?i)bc on ABC no match
(a(?i)b)c on aBc match "aBc"
(a(?i)b)c on abC no match
Rust¶
Verified output of inline_flags_rs_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
== the dot: what it excludes ==
subject "one\ntwo"
one.two no match
(?s)one.two match "one\ntwo"
subject "one\rtwo"
one.two match "one\rtwo"
(?R)one.two no match
== the letter m: line anchors ==
subject "one\ntwo\nthree"
^two$ no match
(?m)^two$ match "two"
(?m)one.two no match
subject "abc\r\ndef"
(?m)^abc$ no match
(?mR)^abc$ match "abc"
== free-spacing: (?x) ==
one line match "2026-09-22"
(?x) match "2026-09-22"
(?x)a b on "a b" no match
(?x)a b on "ab" match "ab"
(?x)a\ b on "a b" match "a b"
(?x)a#b on "a#b" match "a"
== the letter U, Go and Rust only ==
a+ on "aaa" match "aaa"
(?U)a+ on "aaa" match "a"
== inline and scoped modifiers ==
abc on ABC no match
(?i)abc on ABC match "ABC"
(?i:b)c on Bc match "Bc"
(?i:b)c on bC no match
(?i)ab(?-i:cd) on ABcd match "ABcd"
(?i)ab(?-i:cd) on ABCD no match
a(?i)bc on aBC match "aBC"
a(?i)bc on ABC no match
(a(?i)b)c on aBc match "aBc"
(a(?i)b)c on abC no match
== the argument form, for comparison ==
RegexBuilder case_insensitive match "ABC"
its pattern string abc
Every engine measured here that accepts the construct agrees: (a(?i)b)c matches "aBc" and not "abC". The group ends the scope.
| Engine | (?i)… |
a(?i)bc |
(?i:…) |
(?-i:…) |
(a(?i)b)c matches "abC"? |
|---|---|---|---|---|---|
| Perl | yes | yes | yes | yes | no — the group ends the scope |
PCRE2 / ABAP PCRE |
yes | — | yes | yes | no — the group ends the scope |
Python re |
yes | compile error | yes | yes | compile error |
| Ruby | yes | yes | yes | yes | no — the group ends the scope |
| Java | yes | yes | yes | yes | no — the group ends the scope |
Go regexp |
yes | yes | yes | yes | no — the group ends the scope |
Rust regex |
yes | yes | yes | yes | no — the group ends the scope |
| JavaScript | no inline modifiers as of ES2018 |
Python is the exception, and it is the kind of exception to be grateful for: since 3.11 a global inline flag anywhere but the start of the pattern is rejected while parsing, so a(?i)bc never compiles and never quietly means something. The scoped forms (?i:…) and (?-i:…) are fine in Python — those are the ones to reach for.
PCRE2, and therefore ABAP¶
ABAP's PCRE addition is the PCRE2 library, so PCRE2's answers are ABAP's answers, and PCRE2 is on any machine with pcre2grep.
Real run — PCRE2 10.48 (pcre2grep), macOS 26.6.2 x86-64, 2026-09-22. scope.txt holds abc, aBc, abC; lines.txt holds one and two; crlf.txt holds abc and def with CRLF endings.
$ cat scope.txt
abc
aBc
abC
$ pcre2grep -n '(?i)abc' scope.txt
1:abc
2:aBc
3:abC
$ pcre2grep -n '(a(?i)b)c' scope.txt
1:abc
2:aBc
$ pcre2grep -n '(?i:b)c' scope.txt
1:abc
2:aBc
$ pcre2grep -n '(?i)ab(?-i:c)' scope.txt
1:abc
2:aBc
$ pcre2grep -n '(?x) a b c # the three letters' scope.txt
1:abc
$ pcre2grep -M -c 'one.two' lines.txt
0
$ pcre2grep -M -c '(?s)one.two' lines.txt
1
$ pcre2grep -M -c 'one\n^two' lines.txt
1
$ pcre2grep -M -c '(?-m)one\n^two' lines.txt
0
$ pcre2grep -M -c '(?-m)one\ntwo' lines.txt
1
$ pcre2grep -c '^abc$' crlf.txt
0
$ pcre2grep -N CRLF -c '^abc$' crlf.txt
1
Line by line: (?i) at the start reaches the whole pattern and picks up all three subjects; (a(?i)b)c stops at the group's closing bracket and leaves abC out; (?i:b)c and (?i)ab(?-i:c) scope in and back out again; (?x) ignores the spaces and the comment. (?s) turns the dot loose across the line break — 0 matches become 1. The three one\n^two lines isolate m: with PCRE2's multiline option on, a ^ in the middle of the buffer is a line start and the pattern matches; (?-m) takes the option away and it stops; drop the ^ and it matches again, which is what proves the ^ was the reason and not the (?-m).
The last pair is PCRE2's own answer to the CRLF question, and it is neither Python's nor Java's: the newline convention is configurable. With the default the file does not match at all; with -N CRLF it does. In a pattern the same choice is spelled with a start-of-pattern verb such as (*CRLF) or (*ANYCRLF). When a PCRE2 pattern behaves differently in two places, the convention is the first thing to check, before the pattern.
One thing to know before reproducing this: pcre2grep -M switches PCRE2's multiline option on by itself, which is why the m measurement above is written as a (?-m) that takes it away rather than a (?m) that adds it.
The ABAP source¶
ABAP has both spellings — an IGNORING CASE addition on the statement, and the full PCRE2 modifier set inside the pattern — and they are not equivalent.
z_inline_flags.prog.abap in full — pasted here by tools/run_examples.py from the file CI runs.
REPORT z_inline_flags.
" ABAP's PCRE addition is the PCRE2 library, so every inline modifier on this
" page is available inside the pattern: (?i), (?s), (?m), (?x), and the scoped
" forms (?i:...) and (?-i:...). What ABAP adds on top is IGNORING CASE, a
" statement addition that lives outside the pattern.
"
" Source only -- nothing in this repository can run ABAP.
DATA(text) = `Invoice INV-42 and invoice inv-43`.
" 1. The statement addition. It applies to the whole pattern, and it is invisible
" the moment the pattern travels: into a customising table, a message, a log
" line, or a colleague's Python script.
FIND ALL OCCURRENCES OF PCRE `inv-\d+` IN text IGNORING CASE RESULTS DATA(loose).
" 2. The inline modifier. The same answer, but the pattern carries its own
" meaning wherever it is pasted.
FIND ALL OCCURRENCES OF PCRE `(?i)inv-\d+` IN text RESULTS DATA(carried).
" 3. Scoped to a sub-pattern: the three letters are case-insensitive and nothing
" else is. IGNORING CASE cannot express this at all.
FIND ALL OCCURRENCES OF PCRE `(?i:inv)-\d+` IN text RESULTS DATA(scoped).
" 4. Free-spacing. A pattern kept in a Z table is read far more often than it is
" written, and (?x) is the only way to comment one in place.
FIND FIRST OCCURRENCE OF PCRE `(?x) INV - (\d+) # the document number`
IN text RESULTS DATA(readable).
" 5. Multi-line, the ABAP way. IN TABLE runs the pattern against each row, so ^
" and $ anchor per row with no flag involved -- this is what most ABAP code
" means by "multiline", and it is not the (?m) question at all.
DATA rows TYPE STANDARD TABLE OF string WITH EMPTY KEY.
rows = VALUE #( ( `one` ) ( `two` ) ( `three` ) ).
FIND ALL OCCURRENCES OF PCRE `^two$` IN TABLE rows RESULTS DATA(per_row).
" 6. Multi-line, the one-string way. A string that really holds newlines needs
" (?m) before ^ and $ become line anchors, exactly as in Perl.
DATA(multi) = |one{ cl_abap_char_utilities=>newline }two|.
FIND FIRST OCCURRENCE OF PCRE `(?m)^two$` IN multi RESULTS DATA(per_line).
" The obsolete REGEX addition took the same IGNORING CASE but a different
" engine, and it does not agree with PCRE2 at the edges. New code uses PCRE.
Three things are worth taking from that file. IGNORING CASE cannot be scoped: there is no way to say "these three letters only", and (?i:inv)-\d+ says exactly that, inside the pattern. (?x) is the only way to comment a pattern that lives in a customising table, where it will be read far more often than written. And IN TABLE is what most ABAP code means by "multiline" — the pattern runs against each row, so ^ and $ anchor per row with no flag in sight; (?m) is a different question, about a single string that really contains newlines.
Prefer the inline form¶
The recommendation is one sentence: write the flag inside the pattern.
The reason is in the last two lines of the Rust block. RegexBuilder::new("abc").case_insensitive(true) matches "ABC", and the regex's own as_str() is abc — the flag is nowhere in the pattern. Copy that string into a log line, a config file, a database column, a bug report or another language, and the case-insensitivity is gone without a trace. (?i)abc survives all five journeys.
Go makes the argument for you by having no choice: regexp has no flags parameter at all, which is why every Go pattern on this page carries its own (?i) or (?m). A Go pattern pastes into Perl and keeps its meaning. A Python pattern plus re.M does not paste anywhere.
The same reasoning applies inside one language. A pattern in a constant with (?i) written into it cannot be separated from its flag by a refactor; a pattern that depends on the third argument of the call site can.
The letters, side by side¶
| Letter | What it means | Perl | PCRE2 / ABAP | Python re |
Ruby | Java | JavaScript | Go | Rust regex |
|---|---|---|---|---|---|---|---|---|---|
i |
case-insensitive | yes | yes | yes | yes | yes | yes | yes | yes |
m |
^/$ are line anchors |
yes | yes | yes | no — means s |
yes | yes | yes | yes |
s |
. matches \n |
yes | yes | yes | no such letter — use m |
yes | yes | yes | yes |
x |
free-spacing and # comments |
yes | yes | yes | yes | yes | no † | no | yes |
Every yes and every no in that table is printed by a program on this page or by the real run above — except the one marked †. JavaScript's missing x and missing inline modifiers are stated here as prose, as of ES2018, and deliberately not probed: inline modifier groups are a recent addition to the language, so a probe's output would depend on which Node ran it, and a recorded answer key that changes with the runtime is worse than no answer at all. Check your own target before relying on either.
Four more letters belong to one or two engines only, and all four are measured above:
xx— Perl only. Like/x, and also strips whitespace inside character classes.(?d)/UNIX_LINES— Java only. Narrows Java's idea of a line break back to\n, which is to say, makes Java behave like everyone else.(?R)— Rustregexonly. CRLF mode:^/$accept\r\n, and.stops matching\r.(?U)— Go and Rust. Swaps greedy and lazy, soa+matches"a"rather than"aaa". A pattern carrying this letter means the opposite of what it looks like; it is the one flag on this page worth not using.
See also¶
$and the end of the string — what$does when the flags are left alone, and the validation bypass that follows.- Anchors — the chapter these flags act on.
- Who supports what — the same engines, measured on constructs rather than flags, and the family lineages that predict most of the answers here.
- Forward references — the other page in this library about one pattern meaning different things in different engines.
- Resources — where each engine documents its own flag letters.