软硬皆施

记一次换行符引发的事故

那天项目部署的时候,遇到一个很诡异的现象,一度怀疑自己职业生涯到此结束了,后面经排除发现,原来是LinuxWindowsshell脚本换行符的格式问题,导致脚本一直执行不了…(到此,标题所说的事故讲完了…:)

看起来很蠢的问题,溜了 :) …但莎士比亚说过,解决一个问题最好的方式,就是下次不要再犯了。所以我们刨根问底儿,彻底搞清楚换行符在计算机系统中到底是个啥玩意儿。

历史来源

在计算机出现之前,有一种通信设备叫电传打字机(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) 不用切换

实践见真章

我们一步步来实践下上述说的,测试环境是WindowsLinux

  1. Windows下新建个win.txt文件,写一句大白话。上面有听讲的同学应该知道,这里的换行符为CRLF
1
2
talk is cheap,
show me your code.
  1. Linux系统用vim打开刚新建的文件

这看起来不是挺正常的么?和Windows上显示的一样啊。注意这里vim查看文件的时候,会检测换行符,如果所有的换行符都是CRLF,那么它会自动以dos格式来显示文本内容,最下面[dos]里也体现了这一点。

dos(Disk Operating System 磁盘操作系统)和Windows一样采用的是CRLF

  1. 使用cat -A选项查看文本所有的字符

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

  1. 使用cat -v选项显示出非打印字符

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

看到第一行的^M不见了,第二行还是保留。

  1. 再重复第一步看看变化

结果展示vim中多了^M这个符号,左下角也没了[dos]的标识,这里回应了第二点提到的现象,表示vimLinux来显示文本内容。

到这一步为止,我们证实了WindowsLinux环境下不同换行规则带来的差异。

验证下回车符的真实存在

看到社区里的小伙伴做了这个尝试,便参考着做了下,可以直观体现什么是“回车”。

  1. 还原上述的例子,cat -A查看所有字符
  2. sed -i '1s/.*/& ypm/g' win.txt在第一行末尾加上ypm
  3. 再次查看,我们发现上面执行的命令用.*匹配整行的时候,不包括换行符^M,所以ypm加在了第一行的末尾
  4. cat正常查看文本的时候,发现一件奇怪的事情,ypm覆盖了talk,怎么解释?

我们要知道cat普通模式下输出文本内容,会将^M理解为回车。

这就可以解释了,遇到回车符,就像打印探头从右回到了左,ypm这里的四个字符,刚好覆盖了talk四个字符。这就直观解释了什么是回车。

如何规避

谈到如何规避这个差异,其实不同方向上有不同方法,比如针对^M、强制转换文本格式,但个人觉得,能解决其根本问题的,还是在不同系统间,代码编辑的时候,注意到这个格式问题,各大IDE都有自己的解决方案。

  1. 去除回车符号
1
2
3
4
5
cat -v win.txt | tr -d '^M'  > linux.txt
或者
cat win.txt |tr -d '\015' > linux.txt
或者
cat win.txt |tr -d '\r' > linux.txt
1
2
3
4
vim 编辑器中输入
:%s/^M//g
或者
:set fileformat=unix
  1. 终端命令转换
1
dos2unix win.txt

总结

  • 回车符 \r:CR(carriage return)
  • 换行符 \n:LF(line feed)
  • Windows系统遵循最原始的规则,即必须满足回车符+换行符,缺少或者顺序调换都不可,即\r\n
  • Unix系统中遇到换行符\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

PalmerYe wechat
世界很大,圈子很小,欢迎关注,相互进步。
Fork me on GitHub