前言
最近在使用 Git 提交代码的时候,老是碰到一段看起来 “没有任何改动” 的代码,被 diff 检测出异常,很是苦恼,特别是项目紧急的时候,不敢用 VSCode 编辑了,只能用 vim 进行少量代码的修改、上库。
另一件事是 shell 脚本文件行尾换行符异常,导致在 linux 环境下无法正常运行,总是提示什么语法错误。
换了台电脑,git拉取代码之后传到linux设备上,然后git status发现所有文件都被修改了,仔细一看是每行最后都多了一个__^M__。
Git 的多平台换行符问题(LF or CRLF)
Windows下文本文件所使用的换行符是CRLF,而Linux/UNIX/macOS用的是LF。
如果使用 VSCode ,可以通过右下角确认当前文件行尾换行符格式

换行符
关于“回车”(carriage return)和“换行”(line feed)这两个概念的来历和区别。在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。
于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。
后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“ <回车><换行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
Windows创建的文件是 \n\r结束的, 而Linux等类Unix操作系统是\n结束的。所以Unix的文本到Windows会出现换行丢失(ultraedit这类软件可以正确识别); 而反过来就会出现^M的符号了
Windows等操作系统用的文本换行符和Unix/Linux操作系统用的不同,Windows系统下输入的换行符在Unix/Linux下不会显示为“换行”,而是显示为 ^M 这个符号(这是Linux等系统下规定的特殊标记,占一个字符大小,不是 ^ 和 M 的组合,打印不出来的)。Linux下很多文本编辑器(命令行)会在显示这个标记之后,补上一个自己的换行符,以避免内容混乱(只是用于显示,补充的换行符不会写入文件,有专门的命令将Windows换行符替换为Linux换行符)。 Unix/Linux系统下的换行符在Windows系统的文本编辑器中会被忽略,整个文本会乱成一团。

找一份linux上编辑的文档和一份windows上编辑的文档,用notepad++查看,设置显示所有符号,一目了然。

因此经常在两个平台间传输文件经常会在换行符上出问题,网络上有很多转换换行符的方法,随便百度就有,看你用什么编辑器。
这两个符号的差异,会导致文件格式不同。
比如会导致 CRLF 的文件,想要在 Linux 系统执行,就会出问题。
举例来说,我在 Windows 下新建一个 Shell 脚本文件,行尾换行符是 CRLF 格式的,那么此时放到 Linux 系统执行,是会报错,无法执行的。
Git 换行符自动转换
这里先指定两个非官方的概念,方便后面解释与描述:(重要,否则后面看不懂)
- 标准化 指在提交代码到git数据库(本地库) 中将文本文件中的换行符CRLF转为LF的过程
- 转换 指在检出Git数据库代码过程中将文本文件中的换行符LF转换为CRLF的过程
core.autocrlf & core.safecrlf
Git 提供了一个名为 core.autocrlf
的配置,可以自动完成标准化与转换。它的设置方式如下:
git config --global core.autocrlf [true | input | false] # 全局设置 git config --local core.autocrlf [true | input | false] # 针对本项目设置
- true 自动完成标准化与转换
- input 只做标准化操作,不做转换操作
- false 提交与检出的代码都保持文件原有的换行符不变
CRLF 与 LF 混合的文本文件不受此配置控制。 Git 安装后默认为 false
所以,一种规范换行符的方式是这样的
使用 Windows 系统的开发者设置:
git config --global core.aurocrlf true # Windows 打开 autocrlf # pull 时转换为 CRLF ,push 时转换为 LF
使用 Linux/MacOS 的开发者设置:
git config --global core.autocrlf input
由于没有一个绝对有效的算法来判断一个文件是否为文本,所以Git 提供了一项禁止/警告不可逆转换的配置来防止错误的标准化与转换。它主要是影响到多种换行符混合的文件,我们可以手动将其转换为同一种换行符:
git config --global core.safecrlf [true | false | warn]
- true 禁止提交混合换行符的文本文件(
git add
的时候会被拦截,提示异常) - warn 提交混合换行符的文本文件的时候发出警告,但是不会阻止
git add
操作 - false 不禁止提交混合换行符的文本文件(默认配置)

可以运行 git config –list 查看当前配置
若运行git config –list,其中没有出现autocrlf设置,则默认为false,即不对git项目的行尾符做任何处理。
gitattributes 文件
core.autocrlf 的配置依赖于每一位参与项目的开发机器上的配置,这很难确保每个人都能正确配置。于是在规范项目中的换行符方面,还有一套添加配置文件的方案。在项目的根目录下可以添加一个.gitattributes 文件。它的优先级高于core.autocrlf的设置,可以覆盖core.autocrlf的。它类似于 .gitignore 文件,随提交修改生效,一个项目中可以维持一份相同的配置。所以,它能够避免每个开发人员配置不同的问题。
.gitattributes文件的功能不只有配置换行符,所以它的配置相对复杂一下。详细的说明文档可以参考 地址。这里只针对换行符的配置做一下简单的介绍:
每行基本形式:
filter attr1 attr2 ....
filter 代表匹配文件的通配符,在它后面跟着相应的属性,用空格间隔。
filter 的选项比较简单,常见的:
* 匹配所有文件 *.txt 匹配文件名以txt结尾的文件
attr的选择比较多,其中与换行符相关的属性只有几条:

这里给出一个简单的例子来说明一下:
* text=auto # These files are text and should be normalized (convert crlf => lf) *.cs text *.xaml text *.csproj text *.sln text *.tt text *.ps1 text *.cmd text *.msbuild text *.md text # Images should be treated as binary # (binary is a macro for -text -diff) *.png binary *.jepg binary *.sdf binary
除了下面匹配到的文件,剩下的依赖Git 决定是否参与标准化与转换。上面一段是参与标准化与转换的文件;下面一段是不参与标准化与转换的文件;
其实,在文件里只有下面这行配置的时候,就相当于根据操作系统自动填入 core.autocrlf 的设置。
* text=auto