\r: carriage returns, CRLF and stray line endings
\r matches U+000D, the carriage return. Windows text ends every line with \r\n, so a pattern that only knows about \n leaves a carriage return on the end of every line it produces.
At a glance
- Character
- U+000D carriage return
- Windows line ending
- \r\n, in that order
- Safe line split
- /\r?\n/
- Every convention
- /\r\n|\r|\n/, which also handles a lone \r
- For ^ and $ with m
- JavaScript counts a bare \r as a line terminator
- Trimming
- \s includes it, so /\s+$/ removes a trailing \r as well
In detail
The carriage return is invisible, which is the entire problem. Splitting Windows text on /\n/ returns lines that each end in a \r you cannot see. Those values compare unequal to the ones you expected, serialise into JSON as \r, and show up in a diff as a change to a line nobody edited. The fix is to split on /\r?\n/ from the beginning.
If the input might come from any era or any platform, /\r\n|\r|\n/ covers all three conventions including the lone carriage return that older Mac software produced. The order of the branches matters: putting \r first would match the carriage return of a CRLF pair and leave the line feed behind as an empty line.
JavaScript treats a bare carriage return as a line terminator for the ^ and $ anchors under the m flag, alongside the line feed and the two Unicode paragraph separators. That means /^\w+$/gm against "a\r\nb" returns both letters cleanly, with the terminators sitting between the matches. An engine that only breaks on \n behaves differently on the same input, so this is a real difference to check when porting.
For trimming there is nothing special to do, because \s already includes the carriage return. A trim written as /^\s+|\s+$/g removes it along with everything else, which is one more argument for using the broad class rather than a hand written set of space and tab.
Examples
"a\r\nb".split(/\n/); // ["a\r", "b"]
"a\r\nb".split(/\r?\n/); // ["a", "b"]"a\r\nb".match(/^\w+$/gm); // ["a", "b"]
// Keep the carriage return out of a lookahead based line end.
"a\r\nb".match(/\w+(?=\r?\n|$)/g); // ["a", "b"]Things that catch people out
- Splitting on /\n/ leaves a carriage return at the end of every line in Windows text. Nothing displays it, and it survives into whatever you store next.
- /\r\n|\r|\n/ has to list the pair first. Putting the lone \r first splits a CRLF into two, producing a phantom empty line between every real one.
- A pattern ported from an engine that only treats \n as a line terminator will find extra line starts here, because JavaScript's ^ and $ also break on a bare \r under the m flag.
Try it: Regex Tester
Type a pattern and a test string, watch every match highlight as you go, read a plain English breakdown of each token, and check the numbered and named groups it captured.
Regex Tester
LOCAL · Browser-OnlyTest and debug regular expressions with live matching and highlights
Related Regex syntax
All regex syntax →\n: matching a newline, and the double escape trap
\n matches the line feed character. The trap is building a pattern from a string, where the number of backslashes decides what the engine actually sees.
The m flag: multiline anchors
What multiline mode changes about ^ and $, which line terminators JavaScript counts, and why the m flag never makes the dot match a newline.
Regex to trim whitespace, collapse runs and drop blank lines
There is no preset for this one. Four short patterns cover trimming, collapsing, per line trimming and removing blank lines, and each has a different failure.
\0: matching the null character safely
\0 matches U+0000, but only while no digit follows it. \01 is an octal escape without the u flag and a syntax error with it, so \x00 is the safer spelling.
\xhh: matching a character by two hex digits
\xhh takes exactly two hex digits and covers U+0000 to U+00FF. Writing four by mistake gives a two digit escape followed by literal text.
\uhhhh: matching a character by code unit
\uhhhh takes exactly four hex digits and matches one UTF-16 code unit, so anything above U+FFFF needs a surrogate pair or the braced form.
Tools for this
- Regex Tester Free online regex tester - test and debug regular expressions with live matching and highlights
- Regex Playground Regex playground with live match highlighting, named groups, find and replace, and shareable links