记一次换行符引发的事故

31次阅读

共计 2551 个字符,预计需要花费 7 分钟才能阅读完成。

那天项目部署的时候,遇到一个很诡异的现象,一度怀疑自己职业生涯到此结束了,后面经排除发现,原来是 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
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. 把第一行的回车符去掉再看看
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. 去除回车符号
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
vim 编辑器中输入
:%s/^M//g
或者
:set fileformat=unix
  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

正文完
 0