guide/line-endings.md

换行符(CRLF、LF)与看不见的差异 — 所有行都显示为已修改时

整理了肉眼看来相同、diff 却显示每一行都不同的原因——CRLF 与 LF 换行、文件末尾换行符、BOM、制表符与空格、特殊空白、Unicode 规范化——及其解决方法。

最后更新: 2026-09-23

有时打开两个文件,一个字都不差,diff 却说所有行都改了,或者只有第一行、最后一行不同。原因通常是屏幕上看不见的字符。本文整理了这类“看不见的差异”的种类及解决方法。

换行符:CRLF、LF、CR

不同操作系统换行的惯例不同。

写法字节主要使用场景
LF0A (\n)Linux、macOS、大多数开发工具
CRLF0D 0A (\r\n)Windows 记事本等许多程序,电子邮件、HTTP 等互联网协议
CR0D (\r)非常老的 Mac OS(版本 9 以前)

如果一个文件以 CRLF 保存、另一个以 LF 保存,按行比较时每一行末尾那个看不见的 \r 都会被当成差异,看起来就像整个文件都改了。GNU diff 可以用 --strip-trailing-cr、git diff 可以用 --ignore-cr-at-eol 忽略这种差异。

文件末尾的换行符

按照 POSIX 惯例,文本文件的每一行都以换行符结尾。因此很多工具会在最后一行后面也加上换行符,但有些编辑器不加。这种差异在 unified diff 中显示如下:

@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080

\ No newline at end of file 表示紧挨着的上一行末尾没有换行符。上例中内容相同,只是修改后的文件多了结尾换行符。许多代码风格规范要求文件以换行符结尾,可以通过编辑器的“在文件末尾插入换行符”设置来统一。

BOM:只有第一行不同时

UTF-8 文件开头有时会带有 EF BB BF 三个字节,这称为字节顺序标记(BOM)。UTF-8 本身不需要字节顺序,但有些程序会加上它作为“此文件为 UTF-8”的标记。BOM 在屏幕上看不见,所以如果结果显示只有第一行不同,就该怀疑是 BOM。本站在载入文件时会去掉开头的 BOM;开启忽略空白选项后,粘贴文本中的 BOM(U+FEFF)也会被当作空白处理。

制表符、空格与特殊空白

开启忽略空白选项后,这些字符大多会被过滤掉(不过像 U+200B 这样不属于空白字符类别的字符,即使开启选项也可能保留)。把行内比较切换为字符,就能通过高亮找到看不见的字符在什么位置。

Unicode 规范化:同一个字,不同的字节

带声调的拼音字母“é”,既可以写成一个预组合字符(U+00E9),也可以写成“e”加上组合用尖音符(U+0301)。韩文“가”同样既可以是一个完整字符(U+AC00),也可以由辅音 ㄱ(U+1100)和元音 ㅏ(U+1161)组合而成。Unicode 把前者称为 NFC 规范化形式,后者称为 NFD(UAX #15)。两段文本在屏幕上看起来完全一样,比较时却是不同的字符。在 macOS 上创建的文件名到了其他操作系统上,带声调字母或韩文字母被拆开显示,就是典型的例子。解决办法是在比较前把一侧转换为 NFC,使两边一致。

在 git 中统一换行符

如果仓库由多个操作系统的用户共同使用,可以用 git 设置统一换行符。

* text=auto
*.sh text eol=lf
*.bat text eol=crlf

text=auto 会把 git 判定为文本的文件在仓库内的换行符规范化为 LF,eol=lfeol=crlf 则指定检出到工作目录时使用的换行符。

在本工具中试一试

文本对比工具统一换行符默认开启,会把 CRLF、CR、LF 都视为同一种换行,并忽略文件末尾是否有换行符。关闭该选项后,行尾的 CR 显示为 ,没有结尾换行符的最后一行标记为 ,就能看到原本看不见的差异。空白相关选项整理在 忽略空白选项 中,结果中各符号的含义整理在 unified diff 阅读指南 中。

前往文本对比工具