文本文件的每一行结尾用一个或者两个特殊的ASCII字符进行标识,这个标识就是换行符,不同的操作系统中会采用不同的换行符。


1.CR、LF、CRLF

  主要的换行符有三种:LF(Line Feed即换行,转义字符用“\n”表示,十六进制0x0A),CR(Carriage Return 即回车,转义字符用“\r”表示,十六进制0x0D)和CRLF(由两个字符“CR+LF”组成,即“\r\n”,十六进制0x0D 0x0A)。它们的使用范围如下:

  • CR换行符:用于Commodore 8位机、TRS-80、苹果II家族、Mac OS 9及更早版本。
  • LF换行符:用于Multics、Unix、类Unix(如GNU/Linux、AIX、Xenix、Mac OS X、FreeBSD等)、BeOS、Amiga、RISC OS等操作系统中。
  • CRLF换行符:用于DEC TOPS-10、RT-11和其他早期的非Unix,以及CP/M、MP/M、DOS(MS-DOS、PC-DOS等)、Atari TOS、OS/2、Microsoft Windows、Symbian OS、Palm OS等系统中。

  一般操作系统上的运行库会自动决定文本文件的换行格式:程序在Windows上运行就生成CRLF换行格式的文本文件,而在Linux上运行就生成LF格式换行的文本文件。因此,当我们在Windows系统上编辑文本文件时,敲下“enter”键或者写入“\n”,系统会经过一个隐式的转换,将“\n”转换成“\r\n”再写入文件,反过来当我们对文件进行读取时,系统又会进行一个隐式的转换,将读取到的“\r\n”转换为“\n”输出。下面我们来看一个Windows上读写文本文件的示例:



writelines指定换行符 写文件换行符_换行符




我们新建了一个文本文件“test.txt”,并向其中写入了“\n”,




writelines指定换行符 写文件换行符_LF_02




用hexdump命令查看“test.txt”的十六进制码,发现“\n”转换成了0d 0a,也即“\r\n”,




writelines指定换行符 写文件换行符_CR_03




再对“test.txt”进行读取,发现其中的“\r\n”又被转换成了“\n”。



  在Python中,使用os.linesep命令,可以获取当前操作系统的换行符:




Windows:


writelines指定换行符 写文件换行符_CR_04






Linux:


writelines指定换行符 写文件换行符_CR_05




  实际上,自从苹果的Mac OS从第10版转向Unix内核开始,依据不同的文本文件换行符,主流的操作系统可以划分为两大阵营,一方是微软的Windows,使用CRLF作为换行符,另外一方包括Unix、类Unix(如Linux和Mac OS X等)使用LF作为换行符。分属不同阵营的操作系统之间交换文本文件会因为换行符的不同而造成麻烦:编辑器不能识别换行符,可能会显示为特殊字符,如Linux上的编辑器显示的“^M”特殊字符,就是拜Windows的CRLF换行符所赐;或者丢弃换行符,如来自Linux的文本文件,在Windows上打开可能会因为识别不了换行符,导致所有的行合并。


  在不同平台间使用FTP软件传送文件时,在ASCII文本模式传输模式下,一些FTP客户端程序会自动对换行格式进行转换。经过这种传输的文件字节数可能会发生变化,如果你不想ftp修改原文件,可以使用bin模式(二进制模式)传输文本。

2.特殊字符“^M”

  上一节我们已经讲过,Windows系统采用“\r\n”作为换行符,而Linux系统采用“\n”作为换行符,所以当一个用Windows下的换行符的文件在Linux下用VIM打开的时候,单行的最后一个字符就变成了 “\r”,“\r”在 ASCII 码中是0x0D,而0x0D在VIM中被显示为“^M”。如下图:



writelines指定换行符 写文件换行符_LF_06




注意,在用VIM打开时,要加-b选项,否则“^M”字符会被隐藏起来,代之以左下角的[dos]标志,以提示你该文本文件采用的是CRLF换行符。



  现在我们来考虑一个很自然的问题,就是如何去掉上述因跨平台而产生的“^M”字符。方法其实有很多,接下来我们将介绍比较常用的几种:

1. dos2unix:首先在你的Linux机器上安装dos2unix,该命令的作用是将文件中的“\r\n”转换成“\n”。具体的用法可以通过“man dos2unix”命令去了解,这里我们只提两种:
dos2unix -o file #对文件file进行转换,转换完成后写入源文件file,默认采用此种模式
dos2unix -n oldfile newfile #对文件oldfile进行转换,转换完成后写入新文件newfile
与之对称的是unix2dos命令,可将文件中的“\n”转换成“\r\n”,读者自行了解。
2. VIM字符替换:采用VIM的替换命令“%s/^M//g”或者“%s/\r//g”即可。有两点需要注意的是:a).在VIM中,“^M”字符的输入方式是“[ctrl-v-m]”;b).必须采用“vim -b”模式打开文件,否则行末的“^M”字符会因为被隐藏而无法匹配到。
3. VIM “set ff=unix”:该命令可以将带有[dos]标志文件转为unix格式,也即将CRLF换行符替换为LF。有两点需要注意的是:a).区别于dos2unix,该命令并不能将文件中的所有“\r\n”转换为“\n”,只有当文件左下角出现[dos]标志时,该命令才能起作用;b).即使是采用CRLF换行符的文件,如果你用“vim -b”去打开的话,也不会出现[dos]标志,因此该命令也不会起作用。与之对称的是“set ff=dos”命令,可将unix格式的文件转换为dos格式,读者自行了解。
4. sed & tr:采用Linux下的sed或者tr命令可以对文件中的“^M”进行替换或者删除操作,具体用法这里不赘述,读者自行了解。

3.留一个问题

换行符可以看作是行的结束符,也可以看作行之间的分隔符,这两种处理方式之间存在一些歧义。如果换行符被当作分隔符,那么文件的最后一行就不需要再有换行符。但是多数系统的做法是在最后一行的后面也加上一个换行符,也就是把换行符看作是行的结束符。这样的程序在处理末行没有换行符的文件时,可能会存在问题。相反地,有的程序把换行符看作分隔符,就会把最末尾的换行符看作是新行的开始,也就是多出了一个空行。关于这个问题,此处我们先留着,以后再作讲解。