阅读各类词法解析器的代码时,经常看到对 \r\n\u2028\u2029\u0085 这些符号的处理。\r\n 还算熟悉,表示换行,在 mac 中用 \n,在 windows 中用 \r\n。说来惭愧,不仅对于其余3个控制符毫不了解,而且细思 mac 和 windows 中换行符为何不同,也是完全不清楚。通过搜索,在维基百科中发现有对换行符有详尽的描述,故以此文记录。
Newline(通常称为 line ending、end of line(EOL)、line feed 或 line break)是字符编码规范(例如 ASCII 或 EBCDIC)中的控制符或控制符序列,用于表示一行文本的结尾和一个新的开头。 一些文本编辑器在按 ↵Enter键 时会设置此特殊字符。
Newline
(通常称为line ending
、end of line(EOL)
、line feed
或line break
)是字符编码规范(例如 ASCII 或 EBCDIC)中的控制符或控制符序列,用于表示一行文本的结尾和一个新的开头。 一些文本编辑器在按↵Enter键
时会设置此特殊字符。显示(或打印)文本文件时,此控制符使文本编辑器在新行中显示随后的文本。
历史
在19世纪中期,远在电传打印机和电传打字机出现之前,摩尔斯电码操作员或电报员就发明了摩尔斯电码,并使用摩尔斯电码符号对书面文本信息中的空白文本进行编码。发送两个连续的摩尔斯电码“A”,被用来在文本信息表示换行。
后来,在现代电传打字机时代,开发了标准化的字符集控制码来进行空白文本格式化。ASCII 由国际标准化组织(ISO)和美国标准协会(ASA)共同开发,后者是美国国家标准协会(ANSI)的前身。在1963年至1968年期间,ISO 草案标准仅支持将 CR+LF 或 LF 作为换行符,而 ASA 草案仅支持 CR+LF。
CR+LF 通常在许多采用电传打字机(通常是 Teletype 33 ASR)作为终端的早期计算机系统上使用,因为需要该序列才能将打印头移动到下一行行首。 将换行分为两个功能字符来解决打印头不能及时从最右边返回到下一行行首来打印下一个字符的问题。 在 CR 之后打印的字符,通常会在打印头移动至行首的过程中,被打印成页面中的污点。“解决方案是将换行符分为两个字符:CR 是把行移到行首,LF 是把纸上移。”实际上,通常需要发送额外的字符(额外的 CR 或 NUL),这些字符会被忽略,但为打印头移动到左边空白处留出时间。
在此类系统上,能在应用层隐藏此类硬件详细信息的设备驱动程序尚未被开发出来,应用程序必须直接与电传打字机通信并遵循其约定。CR+LF 的组合是为了满足电传打字机的需要。DEC 的大多数微型计算机系统都使用此约定。CP/M 还使用它来在微型计算机使用的相同终端上进行打印。从那里开始,MS-DOS(1981)为了兼容而采用了 CP/M 的 CR+LF,并且该约定被 Microsoft 的更高版本的 Windows 操作系统继承。
Multics 操作系统于1964年开始开发,仅使用 LF 作为其换行符。Multics 使用设备驱动程序将此字符转换为打印机所需的任何顺序(包括额外的填充字符),并且单个字符更易于编程。没有使用似乎更明显的字符 CR,因为 CR 提供了有用的功能,即在一行上叠印一行以产生黑体和删除线效果。 也许更重要的是,单独使用 LF 作为行终止符已被纳入最终的 ISO/IEC 646 标准草案中。 Unix 遵循 Multics 惯例,后来类 Unix 的系统遵循 Unix。 这在 Windows 和类似 Unix 的操作系统之间造成了冲突,因此,一个操作系统上组成的文件不能由另一操作系统正确格式化或解释(例如,用 Windows 文本编辑器(如记事本)编写的 UNIX Shell 脚本)。
表示法
回车(CR)和换行(LF)的概念是紧密相关的,可以单独考虑,也可以一起考虑。在打字机和打印机这样的物理介质中,需要“下移”和“横移”两个动作来在页面上创建一个新行。虽然机器(打字机或打印机)的设计必须分别考虑它们,但软件的抽象可以将它们组合在一起作为一个事件。这就是为什么可以将字符编码中的换行符定义可以将 CR 和 LF 合并成一个(通常称为 CR+LF 或 CRLF)。
Unicode
Unicode 标准定义了一些符合标准的应用程序应该识别为行终止符的字符:
与将所有行终止符转换为单个字符(如 LF)的方式相比,这似乎过于复杂。 但是,Unicode 旨在在将文本文件从任何现有编码转换为 Unicode 并转换回 Unicode 时保留所有信息。 因此,Unicode 应该包含现有编码中包含的字符。 NL 以 0x15 的代码包含在 EBCDIC 中,并且通常映射到 NEL,这是 C1 控制集中的控制字符。因此,它由 ECMA 48 定义,并由符合 ISO/IEC 2022(等效于ECMA 35)的编码识别。C1 控制集也与 ISO-8859-1 兼容。Unicode 标准中采用的方法允许双向转换保留信息,同时仍使应用程序能够识别所有可能的行终止符。
通常不需要识别和使用大于 0x7F 的换行代码(NEL、LS 和 PS)。它们是 UTF-8 格式的多个字节,NEL 字符在 Windows-1252 中被用作省略号(…)字符。例如:
转义符
转义符是组合的字符,它不被作为文本显示,而是被程序拦截,并被假定执行一个特殊的函数。转义符通常用于处理(集合、搜索、替换等)特殊字符。
编程语言
为了便于创建可移植程序,编程语言提供了一些抽象来处理不同环境中使用不同换行序列。C语言提供了转义序列
'\n'(换行符)
和'\r'(回车符)
。但是,这些字符不需要等同于 ASCII LF 和 CR 控制字符。C 标准只保证了两件事:Java、PHP 和 Python 提供 '\r\n' 序列(用于 ASCII CR+LF),与 C 相反,它们保证它们的值分别为 U+000D 和 U+000A。
Java I/O 库不会在输入或输出时透明地将这些转换为与平台特定的换行序列。相反,它们提供了用于写入自动添加本机换行符序列的的函数,以及用于读取接受 CR、LF 或 CR+LF 中任何一个作为行终止符的行的函数(请参见 BufferedReader.readLineFile())。 可以用 System.lineSeparator() 方法得到基础行分隔符。
Python 允许在读取文件、导入模块和执行文件时提供“通用换行支持”。
例子:
换行序列不同带来的问题
不同的换行约定会导致不同类型的系统之间传输的文本文件显示不正确。