那天项目部署的时候,遇到一个很诡异的现象,一度怀疑自己职业生涯到此结束了,后面经排除发现,原来是
Linux和Windows下shell脚本换行符的格式问题,导致脚本一直执行不了…(到此,标题所说的事故讲完了…:)
看起来很蠢的问题,溜了 :) …但莎士比亚说过,解决一个问题最好的方式,就是下次不要再犯了。所以我们刨根问底儿,彻底搞清楚换行符在计算机系统中到底是个啥玩意儿。
历史来源

在计算机出现之前,有一种通信设备叫电传打字机(Teletype Mode),每秒输出10个字符,平均每个字符0.1秒,但有个问题,就是打完一行换行的时候,要消耗0.2秒,在这个时间段内,如果有新字符传来,就将丢失,所以有人为了解决这个问题,在每一行后面加两个字符表示该行结束。
那这里就涉及到两个动作:
- 将打印探头(就如上图的那个刻度上的头子)从右边拉回到左边,这就是回车(carriage return)
- 拉回到左边还不够,还要将探头换行(相对应的就是将纸往上吐一行的距离),这就是换行(line feed)
后面这个概念就被搬到了计算机系统上,But,有人的地方就有分歧,各大系统开始秀了。
Windows系统里,每行结尾是回车+换行(和上述提到的两个动作一致),\r\n;Unix系统,每行结尾只有换行\n;Mac系统,每行结尾只有换行\n;
PS:老
MAC系统用的\r,现在的MAC都是用的\n,和Unix一致了。
不同系统之间的换行规则的不同,导致不同系统下的文件交叉使用的时候,存在不一致,比如最常见的,Unix/Mac系统下的文件在Windows里打开,所有文字会变成一行,原因显而易见。
ASCII码
| 二进制 | 十进制 | 十六进制 | 字符/缩写 | 解释 |
|---|---|---|---|---|
| 00001001 | 9 | 09 | HT (Horizontal Tab) | 水平制表符 |
| 00001010 | 10 | 0A | LF/NL(Line Feed/New Line) | 换行键 |
| 00001011 | 11 | 0B | VT (Vertical Tab) | 垂直制表符 |
| 00001100 | 12 | 0C | FF/NP (Form Feed/New Page) | 换页键 |
| 00001101 | 13 | 0D | CR (Carriage Return) | 回车键 |
| 00001110 | 14 | 0E | SO (Shift Out) | 不用切换 |
实践见真章
我们一步步来实践下上述说的,测试环境是
Windows和Linux。
Windows下新建个win.txt文件,写一句大白话。上面有听讲的同学应该知道,这里的换行符为CRLF。
1 | talk is cheap, |
- 在
Linux系统用vim打开刚新建的文件

这看起来不是挺正常的么?和Windows上显示的一样啊。注意这里vim查看文件的时候,会检测换行符,如果所有的换行符都是CRLF,那么它会自动以dos格式来显示文本内容,最下面[dos]里也体现了这一点。
dos(Disk Operating System 磁盘操作系统)和Windows一样采用的是CRLF
- 使用
cat -A选项查看文本所有的字符

可以看到多了^M$,^M这是Linux等系统下规定的特殊标记,占一个字符大小,不是^和M的组合,只能用Ctrl+v,Ctrl+m按出来;而$不是换行符,可以理解为Linux下用来表示文本结束EOF的符号。
- 使用
cat -v选项显示出非打印字符

- 把第一行的回车符去掉再看看
1 | sed -i '1s/^M//' win.txt |

看到第一行的^M不见了,第二行还是保留。
- 再重复第一步看看变化

结果展示vim中多了^M这个符号,左下角也没了[dos]的标识,这里回应了第二点提到的现象,表示vim用Linux来显示文本内容。
到这一步为止,我们证实了Windows和Linux环境下不同换行规则带来的差异。
验证下回车符的真实存在
看到社区里的小伙伴做了这个尝试,便参考着做了下,可以直观体现什么是“回车”。
- 还原上述的例子,
cat -A查看所有字符 sed -i '1s/.*/& ypm/g' win.txt在第一行末尾加上ypm- 再次查看,我们发现上面执行的命令用
.*匹配整行的时候,不包括换行符^M,所以ypm加在了第一行的末尾 - 用
cat正常查看文本的时候,发现一件奇怪的事情,ypm覆盖了talk,怎么解释?
我们要知道
cat普通模式下输出文本内容,会将^M理解为回车。
这就可以解释了,遇到回车符,就像打印探头从右回到了左,ypm这里的四个字符,刚好覆盖了talk四个字符。这就直观解释了什么是回车。

如何规避
谈到如何规避这个差异,其实不同方向上有不同方法,比如针对^M、强制转换文本格式,但个人觉得,能解决其根本问题的,还是在不同系统间,代码编辑的时候,注意到这个格式问题,各大IDE都有自己的解决方案。
- 去除回车符号
1 | cat -v win.txt | tr -d '^M' > linux.txt |
1 | vim 编辑器中输入 |
- 终端命令转换
1 | dos2unix win.txt |
总结
- 回车符
\r:CR(carriage return) - 换行符
\n:LF(line feed) Windows系统遵循最原始的规则,即必须满足回车符+换行符,缺少或者顺序调换都不可,即\r\nUnix系统中遇到换行符\n就会进行回车+换行的操作,而回车符\r会作为特殊字符^M显示
没想到这么简单的问题,扯了这么多,其实很多小伙伴也都遇到过这个问题,社区里也经常有因为换行符导致的“血案”,希望没有因此酿成之前那个程序猿小哥手误导致的几千万的损失。在运维、DB等领域,这些“小问题”可能会被放大,因此还是需要引起重视,虽然有很多规避手段,但在代码编写的初期,就应该养成习惯关注到这一点,排查问题的时候这同样是一个方向。
PS: 可能会有不严谨的地方,也欢迎大家讨论,轻喷…
参考
http://www.ruanyifeng.com/blog/2006/04/post_213.html
https://www.cnblogs.com/linuxnote/p/3753153.html
https://blog.csdn.net/zhangguangyi888/article/details/8159601