数学是神造科学,计算机是人造科学

所以在计算机的底层软件中,有着数不清的 tricks,让人很无奈,但又无法避免。譬如 Big-endian 和 Little-endian 的问题,据说 UNIX 曾经移植到另外一台不同 “endian” 的机器上,结果系统启动显示的是 NUXI 而不是 UNIX;再比如 ASCII 和 Unicode,以及各种各样地方编码纠结的关系;连最简单的 hello world,放到不同的平台上,生成的字符串也是不同的,考虑下面的程序:

#include <iostream>
using namespace std;

int main( int argc, char *argv[]) {
  cout << "Hello\nWorld";
}

这个程序如果在 *nix 平台下执行,生成的字符串的长度是 11:

lox@lox-pad ~/tmp> clang++ main.cxx -o main
lox@lox-pad ~/tmp> ./main | wc -c
11
lox@lox-pad ~/tmp> ./main | od -c
0000000   H   e   l   l   o  \n   W   o   r   l   d
0000013
lox@lox-pad ~/tmp>

但是如果放到 Windows 上执行,那么程序生成的字符串的长度是 12,字符串的值是 "Hello\r\nWorld" ,so,what is '\r'

细究起来,我们发现,一个小小的 newline 字符也有如此多的故事,或者说,历史包袱。 Wikipedia 里面给出了完整的解释。简单来讲, '\n''\r' 的概念来源于早期的打字机, '\n' 代表 “line feed”,表示打字机打完一整行后相对纸面向下移动一整行的距离,而 '\r' 则代表 “carriage return”,就是控制打字机重新回到一行的行首, "\r\n" 连在一起用才会使得打字机移动到新的一行的行首开始工作。而在后来的 OS 设计中,由于在 TTY 终端并没有实体的打印机控制操作,所以在设计上将 "\r\n" 进行了简化处理,只是不同的 OS 采取的策略不一样。我们仅以 Windows、Linux、Mac这三种最流行的系统来做说明。

  • Windows – "\r\n"
  • Linux – '\n'
  • Mac – '\r'

所以 Linux 和 Mac 上的多行文本文件用 Windows 的 Notepad 打开,常常会出现多行并做一整行的现象,而 Windows 上的文本文件用 Linux 上的 Vim 或者 Emacs 等editor来访问,往往会看到奇怪的 '^M' 字符,事实上这个 '^M' 并不是 '^' + 'M' 的组合,它就是 Windows 中的 '\r' 。在 Linux 终端中你可以通过输入 Ctrl+v Ctrl+m 来获得 '\r' 这个输入。

那么,我们怎样去掉这写讨厌的影响心情和版面美感的 '\r' 呢?方法有很多,最简单的,安装 dos2unix 这个小软件,然后直接 dos2unix filename 即可,Arch Linux 可以通过 pacman -S hd2u 来获取 dos2unix,反过来也有 unix2dos 这样的命令。

除了 dos2unix,利用 Linux 本身的 shell 工具,也可以达到目的,比如:

  • sed 's/\^M//g' filename
  • sed 's/\r//g' filename
  • cat filename | tr -d '\r' > newfile
  • vim: %s/\^M//g

最后需要注意的是,C 语言的标准库针对这种情况做了专门的规定,并针对性的规定了 text mode 和 binary mode,这种小的 trick 平时不需要注意,但是没准某一天就会冒出来拷打一下你的耐心。