Multiline text and logs¶
One line: A pattern over a text with line breaks in it has to answer three questions the pattern does not ask by itself — do ^ and $ mean lines, does . cross a newline, and what is a newline — and the eight engines here split on all three, with Ruby using the other engines' letters for different things and Java and JavaScript alone treating a \r as part of a line ending.
Level: 201 · you have a log file, a config file, or anything with more than one line
The three questions¶
| Question | The flag | Default | Where it differs |
|---|---|---|---|
Do ^/$ match at line boundaries? |
m / (?m) / re.M / MULTILINE |
no — except Ruby: always yes | Ruby has no flag to turn it off |
Does . cross a line break? |
s / (?s) / re.S / DOTALL |
no | Ruby spells it m, and refuses (?s) |
| What counts as a line break? | — | \n only |
Java and JavaScript: also \r, and \r\n as a unit for the anchors |
Measured¶
Verified output of topic_multiline_text_and_logs_sh.sh — regenerated by tools/run_examples.py, never hand-typed.
construct python perl node ruby java go rust fancy
line anchors, no flag no no no b no no no no
line anchors, (?m) b b - b b b b b
line anchors, node m flag no no b b no no no no
CRLF line, (?m)^b$ no no - no b no no no
CRLF line, \r?$ b\r b\r - b\r b b\r b\r b\r
dot across lines a\nb\nc a\nb\nc - - a\nb\nc a\nb\nc a\nb\nc a\nb\nc
dot across lines, Ruby (?m) no no - a\nb\nc no no no no
\R for any line break - a\r\nb no a\r\nb a\r\nb - - -
first line only first first no first first first first first
last line only - second no second second second second second
last line, Python's \Z second second no second second - - second
Reading the table¶
- Rows 1–3, line anchors.
^b$ina\nb\ncfinds the middle line only in Ruby without a flag; with(?m)— or JavaScript'smflag — in everyone. Eight engines, one feature, two spellings and one default. - Rows 4–5, CRLF. The same
(?m)^b$ona\r\nb\r\ncmatches only in Java: its$recognises\r\nas a line ending. The other six treat the\ras the last character of the line, sob$fails. Adding\r?$— row 5 — makes them match, and the match then contains the\r(b\r), which is now in whatever you extract. Java, having handled it properly, returnsb. JavaScript is-on both rows only because it has no(?m); the flags lesson passes the flag and shows it agreeing with Java. - Rows 6–7, dot.
(?s)a.*ccrosses the lines in six engines; Ruby refuses(?s). Ruby's spelling is(?m), row 7, and in the other six(?m)does nothing for the dot. - Row 8,
\R. The one escape that matches\n,\r,\r\n(and the other Unicode line separators) as one unit: Perl, Ruby and Java. Elsewhere,\r?\nfor CRLF-or-LF, and accept that a lone\ris not covered. - Rows 9–11, the first and last line.
\A[^\n]*takes the first line in seven engines; JavaScript matched nothing because\Ais the letter A there —^withoutmis its spelling.[^\n]*\ztakes the last line where\zexists, and Python 3.12's spelling is\Z(row 11), which Go and Rust refuse.
The idioms¶
| Job | Portable pattern | Notes |
|---|---|---|
| One line at a time | split the text on \r?\n first, then match each line with no flags |
the only shape that is identical in all eight engines |
A line that starts with ERROR |
(?m)^ERROR\b |
m flag in JavaScript; nothing in Ruby |
| Everything between two markers, across lines | (?s)BEGIN(.*?)END |
(?m) in Ruby; [\s\S]*? in JavaScript when the flag is out of reach |
| Trailing whitespace on any line, CRLF-safe | (?m)[ \t]+(?=\r?$) |
the lookahead keeps the \r out of the match |
| Normalise line endings first | replace \R (or \r\n?) with \n |
after which every question above has one answer |
Keywords on this page¶
^ · $ · \A \z \Z · . · Inline flags · Shorthand classes — for \R · (*CRLF).
See also¶
- Flags, and where you can turn one on — the lesson, with every engine's CRLF row and Java's
(?d). $is not the end of the string.- Command-line tools — where the tool reads line by line for you.