Skip to content

. — any character, except the ones it is not

One line: . matches one character that is not a newline — where "newline" is \n alone in Python, Perl, Ruby, Go and Rust, and also \r in JavaScript and Java; the flag that makes it match newlines is s in six engines and m in Ruby; and whether an emoji is one character or two depends on JavaScript's u flag.

Level: reference · one construct, eight engines

What it means

Three questions hide inside "any character", and engines answer each one differently:

  1. Which line terminators does it refuse? \n everywhere. \r too in JavaScript and Java, whose definition of a line break is CRLF-aware. So a.b matches a\rb in six engines and not in those two.
  2. What flag lets it match them? (?s) / re.S / DOTALL / the s flag — except in Ruby, where the letter is m, and (?s) is a compile error.
  3. What is one character? A code point, in every engine that operates on decoded text — except JavaScript without the u flag, which operates on UTF-16 code units and sees an emoji as two of them.

Measured

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

construct               python     perl       node       ruby       java       go         rust       fancy
ordinary                axb        axb        axb        axb        axb        axb        axb        axb
across \n               no         no         no         no         no         no         no         no
across \n with (?s)     a\nb       a\nb       -          -          a\nb       a\nb       a\nb       a\nb
across \n, node s flag  no         no         a\nb       no         no         no         no         no
across \n with (?m)     no         no         -          a\nb       no         no         no         no
across \r               a\rb       a\rb       no         a\rb       no         a\rb       a\rb       a\rb
one astral character    \u{1F600}  \u{1F600}  no         \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}
astral, node u flag     \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}  \u{1F600}
escaped dot             no         no         no         no         no         no         no         no
dot inside a class      no         no         no         no         no         no         no         no

Reading the table

  • Row 2 is unanimous: no engine's plain . crosses \n.
  • Row 3, (?s). Six engines cross it. JavaScript is - (no inline flags — row 4 passes s as an argument and it works). Ruby is -: (?s) does not exist there. Row 5 is Ruby's spelling — (?m) — and it is the only engine where (?m) changes what . does. In the other six, (?m) is about ^ and $, and . still stops at the newline.
  • Row 6, a\rb. Python, Perl, Ruby, Go and Rust match across a carriage return; JavaScript and Java refuse. A pattern tested on Unix line endings and shipped against CRLF files behaves differently in exactly those two engines — which happen to be the two most likely to be running on the same Windows machine that produced the file.
  • Rows 7–8, one astral character. Seven engines see U+1F600 as one character and match ^.$. JavaScript without u sees two code units and fails; with u it agrees. This is the same fault line as \p{…} needing u — JavaScript has two string models and the flag picks one.
  • Rows 9–10. Escaped, or inside a class, . is a full stop. Both no cells are correct: axb contains no full stop.

The trap

.* as "everything". It is everything on one line, and on Windows input in JavaScript or Java it is everything up to the \r. The fix is not to add \r to a class — it is to decide whether you meant a line or the whole text, and say so with (?s) (or Ruby's (?m)), or to use [\s\S] in JavaScript when the flag cannot be reached.

Where it is measured in depth

See also