Line Endings Explained: CR, LF, and CRLF
How Typewriter Hardware Created Today's Text Interoperability Bugs
In 1963, the American Standards Association assigned decimal value 13 (0x0D) to Carriage Return and decimal value 10 (0x0A) to Line Feed within the 7-bit ASCII standard. That dual character design reflected mid-twentieth-century teleprinter hardware, which required two distinct physical actions to start a new line of text. Modern software systems still inherit these hardware decisions, producing cross-platform syntax errors, version control conflicts, and protocol failures when systems disagree on line termination.
Mechanical Teletypes and the Physical Split of CR and LF
Teletype Model 33 machines in the early 1960s required roughly 200 milliseconds to reposition their heavy printing carriage back to the left margin. During that physical movement, sending another printable characters caused the mechanical head to print characters mid-flight across the page, smearing ink diagonally across the paper. The mechanical sequence required two separate signals: Carriage Return (CR, 0x0D or decimal 13) moved the cylinder horizontally back to position zero, while Line Feed (LF, 0x0A or decimal 10) rotated the platen vertically by one line position.
Telecommunication engineers included both commands in the ASCII table standard ASA X3.4-1963. In early electromechanical terminals, sending CR followed by LF allowed the motor enough time to complete physical carriage travel while paper advance commenced. Omitting either byte caused hardware jamming or distorted printouts. Teletype operators sometimes inserted extra null bytes after a CR on high-speed lines to give the carriage sufficient settling time before printing resumed.
The distinction between the two control characters remains visible in low-level byte streams:
| Character | Acronym | Escape Sequence | Decimal Value | Hexadecimal Value | ASCII Keyboard Shortcut |
|---|---|---|---|---|---|
| Carriage Return | CR | \r |
13 | 0x0D |
Ctrl+M |
| Line Feed | LF | \n |
10 | 0x0A |
Ctrl+J |
Operating System Split: Unix, MS-DOS, and Classic Mac OS
Multics developers in the late 1960s decided that storing two bytes for every line ending wasted precious main memory and disk capacity. When Dennis Ritchie and Ken Thompson created Unix at AT&T Bell Labs, they adopted the Multics convention of using a single Line Feed (0x0A) to mark the end of a line in text files. The Unix TTY terminal driver translated this single character into the two-byte CR-LF sequence at runtime whenever text was output to physical teletype hardware. Software applications stored clean single-byte line terminators in memory while hardware drivers handled physical device quirks.
Gary Kildall designed CP/M in 1974 for 8-bit Intel 8080 microcomputers. CP/M maintained direct compatibility with Teletype terminals by storing line endings as explicit CR-LF byte pairs in text files. When Tim Paterson wrote QDOS in 1980 (which Microsoft acquired and renamed MS-DOS 1.0), he replicated CP/M file system behavior and system call conventions to ensure existing CP/M software like WordStar could be ported easily. Microsoft Windows retained MS-DOS file system conventions, making \r\n the standard text line terminator across all 16-bit, 32-bit, and 64-bit Windows releases.
Apple took a third path with the Macintosh in 1984. Apple engineers picked Carriage Return (0x0D) alone as the end-of-line character for System 1 through Mac OS 9. When Apple released Mac OS X in 2001 based on the NeXTSTEP architecture and BSD Unix kernel, Apple abandoned the CR-only convention and standardized on Unix LF (0x0A). Legacy Classic Mac text files remain the primary source of lone CR bytes in modern software development.
Network Protocols: Why the Internet Standardized on CRLF
Early network protocol authors required a single canonical line representation so heterogenous computer architectures could exchange commands reliably across the ARPANET. RFC 822 (published in 1982 for internet text messages) and RFC 959 (FTP) specified that control lines and ASCII data streams must terminate with explicit CRLF byte pairs regardless of the operating system operating at either endpoint.
HTTP maintained this requirement in RFC 2616 and updated specifications like RFC 9110. An HTTP response header block must end with a CRLF sequence, and an empty CRLF line separates the HTTP headers from the response payload body:
HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 42\r\n\r\n<html><body>Hello World</body></html>
Web servers parsing incoming request headers look for the byte values 0x0D 0x0A 0x0D 0x0A to identify the exact boundary where header metadata ends and payload content begins. Web servers using strict parsing fail or return HTTP 400 Bad Request responses when clients send headers terminated only with LF. Security vulnerabilities like HTTP Response Splitting occur when software applications fail to sanitize user inputs containing embedded CRLF sequences, allowing attackers to inject arbitrary HTTP headers or fabricate duplicate responses.
Common Cross-Platform Failures and Diagnostic Commands
Opening a Windows-created shell script on a Linux server frequently results in mysterious script execution errors. When bash reads a file containing Windows CRLF line endings, it interprets the \r byte as part of the command string rather than whitespace formatting. A script starting with #!/bin/bash\r causes Linux kernel script loaders to search for an interpreter named /bin/bash\r on disk, returning errors such as No such file or directory or syntax error near unexpected token '$'\r''.
Software developers inspect line endings using command-line utilities that expose non-printable control byte values in hexadecimal or visual escape notations:
- file: The Linux
file textfile.txtcommand reportsASCII text, with CRLF line terminatorsif CR bytes are present. - hexdump: Running
hexdump -C textfile.txtdisplays raw byte values in memory. A line ending showing0d 0aconfirms CRLF, while0aconfirms LF. - cat: The command
cat -v textfile.txtdisplays CR bytes as^Mvisual control characters at the end of every line. - dos2unix: Executing
dos2unix filenamestrips all0x0Dbytes directly preceding0x0Abytes in place. - unix2dos: Executing
unix2dos filenameinserts a0x0Dbyte before every isolated0x0Abyte.
Reading raw byte structures helps clarify how computers store text from bits to characters across varying system architectures and ASCII standard implementations.
Language Parsers, I/O Buffers, and Git Handling
Programming languages abstract physical line ending differences using platform-dependent text stream drivers. The C standard library function fopen() distinguishes between text mode ("r") and binary mode ("rb"). On Windows platforms using Microsoft Visual C++ runtime libraries, reading a text file opened in "r" mode automatically strips \r bytes from incoming streams, presenting C code with isolated \n characters in buffer memory. Writing \n to a text mode file stream automatically injects \r\n onto the physical disk. Opening a file in "rb" binary mode bypasses this automatic translation layer, reading the exact on-disk byte sequence into memory.
Python 3 manages line endings using the universal newlines parameter inside its built-in open() function. By default, open(file, mode='r', newline=None) converts \r\n, \n, and \r inputs into standard \n string characters inside application code. Setting newline='' disables translation, leaving line terminator bytes unchanged. Developers converting raw encoding formats rely on dedicated text converter tools when raw bytes must be verified prior to parsing.
Version control systems like Git manage cross-platform line termination through repository settings. When multiple developers work on the same repository using different operating systems, unmanaged line endings cause Git to report every line in a file as modified despite no visible content changes. Git provides the core.autocrlf configuration setting to standardize line endings in repository trees:
git config --global core.autocrlf true: Windows developers use this setting to convert LF line endings to CRLF during repository checkout, while converting CRLF back to LF during code commit operations.git config --global core.autocrlf input: Linux and macOS developers use this setting to convert any accidental CRLF line endings to LF on commit while keeping local repository checkouts as LF.git config --global core.autocrlf false: Disables automatic conversion, preserving byte streams exactly as authored.
Project teams enforce consistent line endings by creating a .gitattributes file at the root of a Git repository. Adding the line * text=auto forces Git to normalize all text files to LF within the remote repository object database, while permitting local operating system checkouts to adapt as specified.
References
- ANSI X3.4-1986: American National Standard for Information Systems - Coded Character Sets - 7-Bit American National Standard Code for Information Interchange (ASCII)
- RFC 2821: Simple Mail Transfer Protocol (SMTP)
- RFC 9110: HTTP Semantics (Field Syntax and Line Termination)
- ISO/IEC 646:1991: Information technology - ISO 7-bit coded character set for information interchange