The same text file opens as a single line on Windows, produces a Git diff covering every line, or refuses to run with bad interpreter. In most cases the cause is the same: line endings. Once you understand CRLF and LF, all of these collapse into one problem.
What CR and LF originally meant
- CR (Carriage Return,
\r, 0x0D) - move the print head back to the start of the line. - LF (Line Feed,
\n, 0x0A) - advance the paper by one line.
Mechanical terminals needed both actions, so both bytes were sent. Windows inherited that pair as CRLF. Unix decided LF alone was enough. Neither is more correct; it is a historical split.
| Name | Bytes | Where it is used |
|---|---|---|
| LF | 0A | Linux, macOS, most programming languages |
| CRLF | 0D 0A | Windows, HTTP headers, many network protocols |
| CR | 0D | Mac OS 9 and earlier (rare today) |
Where it actually breaks
A shell script will not run
$ ./deploy.sh
bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory
The first line is #!/bin/bash\r rather than #!/bin/bash, so the system looks for an executable named /bin/bash\r. The ^M in the message is the giveaway.
Git reports every line as changed
You changed nothing, yet the diff covers the whole file. An editor normalized the line endings on save. Diffs compare line by line, so changing every line ending marks every line as modified, which makes review impossible.
Invisible characters in config values
Saving a .env file as CRLF appends a carriage return to each value. PORT=8080\r does not compare equal to 8080. The configuration looks right but the program misbehaves, and nothing in the file shows why.
How to inspect and convert
# Identify the current line endings
$ file deploy.sh
deploy.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
# Look at the bytes directly
$ head -1 deploy.sh | od -c | head -2
0000000 # ! / b i n / b a s h \r \n
# Convert CRLF to LF
$ sed -i 's/\r$//' deploy.sh
# Or use dos2unix if available
$ dos2unix deploy.sh
Prevent recurrence in Git
# .gitattributes
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.png binary
text=auto eol=lf normalizes anything Git detects as text to LF in the repository. Windows batch files need CRLF, so they are listed as explicit exceptions. core.autocrlf is a per-user setting, and .gitattributes in the repository takes precedence. Use .gitattributes when you need the whole team to agree.
Frequently Asked Questions
Should I use CRLF or LF?
Standardize on LF inside the repository. Windows editors often save as CRLF, so most teams add a .gitattributes file with text=auto eol=lf so that committed content is normalized to LF. Keep exceptions for files that require CRLF, such as Windows batch files.
Why does my shell script fail with bad interpreter?
Because the file is saved with CRLF, the shebang line ends up as #!/bin/bash followed by a carriage return. The system then looks for an executable literally named /bin/bash\r, which does not exist. Converting the file to LF fixes it.
Why does Git show every line as changed?
Because the line endings were converted across the whole file. Diffs compare line by line, so if every line ending changes, every line is reported as modified even though the content is identical. Check for a line ending conversion before committing.