Line Ending
The invisible characters marking where a line of text ends, split three ways since the teletype era between Unix, Windows, and classic Mac OS, and still breaking scripts and diffs.
A line ending is the character, or pair of characters, that marks the end of a line in a text file. Three conventions survive, and text moving between systems trips over the differences.
The split goes back to teletypes. A teleprinter needed two separate motions to start a new line: a carriage return, which sent the print head back to the left margin, and a line feed, which advanced the paper. ASCII gave each its own control code, CR and LF; see Character Encoding. Sending both also bought time, since the carriage took longer to return than one character took to arrive.
Operating systems then chose differently:
- Unix used LF alone, leaving the terminal driver to produce both motions. Linux and macOS follow it today.
- CP/M and then MS-DOS kept both, CR then LF, and Windows inherited the pair, often written CRLF.
- Classic Mac OS used CR alone until Mac OS X moved to Unix conventions.
Internet protocols standardized on CRLF, so the header lines of HTTP and email end with both characters whatever system sends them.
The Unix translation still happens. When a program writes LF to a terminal, the terminal driver outputs CR and LF, a setting a program can switch off. Raw-mode programs that forget this produce text that staircases across the screen; see Pseudoterminal.
The everyday failures:
- Scripts. A shell script saved with Windows endings fails with a baffling error naming an interpreter that doesn't exist, because the kernel reads the interpreter path with an invisible CR stuck to its end; see Shebang.
- Diffs. An editor that silently converts endings changes every line, burying the real change in a Diff that marks the whole file as rewritten.
- The last line. POSIX defines a line as ending with a newline, so a file whose final line lacks one is technically incomplete. Some tools drop or merge that line, and diffs flag it.
- Data files. A CSV field can legally contain line breaks, so splitting on newlines isn't safe parsing.
Version control tries to smooth this over. Git can convert endings on checkout and commit, configured per user or, more reliably, per repository through a .gitattributes file. Leaving it to personal settings is how one commit rewrites every line in a project.
Unicode added further line terminators, including dedicated line and paragraph separators. JavaScript once treated those two as line breaks inside string literals while JSON allowed them, so some valid JSON wasn't valid JavaScript until the language changed in 2019.
The characters are invisible in most editors, so these bugs survive.
See also2
ANSI Escape Code
The in-band byte sequences, starting with the escape character, that tell a terminal to move the cursor, change color, or clear the screen, inherited from 1970s video terminals and still how every command-line tool draws.
Systems & Tooling12 connections
Code Formatter
A tool that rewrites source code into one canonical layout automatically, ending style debates in code review and keeping diffs focused on what changed.
Version Control & Delivery14 connections
Linked from6
- Base64Data & Content
A way to write arbitrary bytes using 64 safe text characters, so binary data can travel through email, JSON, URLs, and other channels built for text, at the cost of a third more size and with no secrecy at all.
- Code FormatterVersion Control & Delivery
A tool that rewrites source code into one canonical layout automatically, ending style debates in code review and keeping diffs focused on what changed.
- CSVData & Content
The plain-text table format of rows separated by line breaks and fields separated by commas, universal and simple until quoting, encoding, and spreadsheets get involved.
- DiffVersion Control & Delivery
The listing of changes between two versions of a file, the algorithms that compute it, and the unified format that code review, patches, and merges are built on.
- GitignoreVersion Control & Delivery
The file of patterns telling Git which files to leave untracked, such as build output, dependencies, editor clutter, and local secrets, along with the traps of files that were already tracked and secrets already committed.
- ShebangSystems & Tooling
The "#!" line at the top of a script that tells the kernel which interpreter should run it, so a text file can be executed like a compiled program, and the portability traps around it.