$ — the end, plus one newline you did not ask for¶
One line: $ matches at the end of the subject — and also just before a final newline in Python, Perl, Java and Ruby, and at the end of every line in Ruby, and only at the true end in JavaScript, Go and Rust; three definitions of "end" behind one character, none of them an error.
Level: reference · one construct, eight engines
What it means¶
Where $ succeeds |
Python, Perl, Java | Ruby | JavaScript, Go, Rust regex, fancy-regex |
|---|---|---|---|
| End of the subject | yes | yes | yes |
Before a final \n |
yes | yes | no |
Before any \n |
only under (?m) / re.M / MULTILINE |
yes, always | only under (?m) or the m flag |
The middle column is the Perl heritage: $ was designed for lines read with their newline still attached, so /foo$/ would match a line that said foo. Every engine that copied Perl's syntax had to decide whether to copy that, and they split three ways.
Measured¶
Verified output of kw_dollar_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
plain end a a a a a a a a
before a final newline a a no a a no no no
before a middle newline no no no a no no no no
(?m) line end a a - a a a a a
node m flag no no a a no no no no
\z instead - no no no no no no no
\z in JavaScript - no az no no no no no
escaped dollar $5 $5 $5 $5 $5 $5 $5 $5
mid-pattern $ no no no no no no no no
Reading the table¶
- Row 2,
a$againsta\n. Python, Perl, Ruby and Java saya: the newline is stepped over. JavaScript, Go and both Rust crates sayno. Same pattern, same subject, and a form field that ends in a newline is accepted by one group and rejected by the other. - Row 3,
a$againsta\nb. Only Ruby matches — its$is a line anchor. The other four "newline-tolerant" engines tolerate exactly one newline, and only at the very end. That distinction is why"1\nrm -rf /"does not get through^\d+$in Python, and does in Ruby; the lesson page runs the payloads. - Rows 4 and 5.
(?m)— or themflag in JavaScript, which cannot spell an inline flag — turns$into a line anchor everywhere. After that, all eight agree. - Rows 6 and 7,
\z. The anchor that means the true end answersnofora\nin every engine that has it. Python's column is-: Python 3.12 has no\z(3.14 added it — the probe runs 3.12 because CI does). JavaScript'snoin row 6 is not agreement, and row 7 is the tell:a\zmatches the textaz, because\zis the letter z there. - Row 8.
\$is a literal dollar everywhere. Row 9: an unescaped$in the middle of a pattern is an anchor in the middle of a pattern, which can never succeed —a$bmatches nothing in all eight.
The trap¶
The subject with a trailing newline. Files end in one, input() in some languages strips it and in others does not, and HTTP form fields can carry one. ^\d+$ was written to accept a number; in four of these eight engines it also accepts a number followed by a newline, and code downstream that does int(value) succeeds while code that does value == "42" fails. Nothing in the pattern says which you get.
The rule that survives every engine here: for validation, anchor with \A and \z where they exist, or use the language's whole-string call — re.fullmatch, String.matches, \A…\z in Ruby — and never ^…$.
Where it is measured in depth¶
$is not the end of the string — the payloads, the\Zcollision, and every engine's whole-string alternative.- Flags, and where you can turn one on — the
mthat means two different things.
See also¶
^·\A,\z,\Z— the anchors that fix this.- Replacement references — the other
$, the one in the replacement string, which is a different language again. - Validating input · Security.