“回车换行(CRLF)已过时,应废除!”SQLite 之父的公开呼吁引发热议
“回车换行(CRLF)已过时,应废除!”SQLite 之父的公开呼吁引发热议
SQLite之父D. Richard Hipp近日公开呼吁废除CRLF(回车换行),这一提议引发了开发者社区的广泛讨论。本文将详细介绍CRLF的历史渊源、技术细节以及这场技术争论的来龙去脉。
作为一名程序员,想必你对CRLF并不陌生。CRLF,全称Carriage Return Line Feed,中文翻译为回车换行。它由两个字符组成:CR (\r,回车) 和 LF (\n,换行),其中回车是将光标移动到当前行的最左侧,而换行则是将光标下移一行。这里还需要提及的一个概念是——新行 (NL,NewLine),它是指将光标下移一行,并移动到当前行的最左侧。
CRLF的存在主要是为了兼容不同操作系统的文件格式。通常,Windows使用CRLF作为换行符,而Unix/Linux和macOS只使用LF。然而,在实际开发中,CRLF和LF的差异常常导致不少开发团队在处理文件时出现令人头疼的编码错误和冲突。越来越多的开发者认为:CRLF已过时,应该被直接废除。
SQLite之父发起呼吁
这则声明由美国软件开发者D. Richard Hipp发起的,他不仅创建了SQLite开源嵌入式关系数据库,也开发了分布式版本控制系统Fossil和网络服务器Althttpd等软件。
D. Richard Hipp表示,“回车”和“新行”都是有用的控制字符。NL(新行)是最常见的操作,表示开始新的一行并从行首开始写。单独的CR有时也有用,尤其当你想覆盖已经写好的文字时。而LF(换行)基本上没什么用。没有人希望在一行的中间停止,然后向下移动一行并从同一列继续写。没有任何实际程序会这么做。
LF之所以存在,是在计算机终端还是电传打印机的时候遗留下来的东西。D. Richard Hipp称,LF诞生于大约70年前的机械电传打字机时代。当时的电传打字机没有使用晶体管,而是完全由齿轮、凸轮、马达、继电器和伺服装置组成的。它们非常神奇,可以将通过两根铜线传输的二进制代码转换为纸上的打印文本。
电传打字机就像是普通打字机一样工作,每秒打印大约5个字符。打印头是一个包含字母的圆柱体或椭圆形小球。打印头与纸之间有一个浸有墨水的布带。为了打印一个字符,打印头会旋转到正确的位置,然后向前撞击,使布带上的墨水在纸上形成所需字符的形状。每打印一个字符,整个打印头机构(小球、墨带以及各种控制凸轮和齿轮)都会向右移动一个字符的位置。这一切每秒发生五次。这些机器运作时噪音很大,并且会明显震动。
在一行文本的末端,打印头必须返回到最左侧。打印头移动得很快,但移动到最左边仍需要时间。当时没有内存,所以打印头必须在下一个字符到来之前完全移到左边。为了实现这一点,NL(新行)操作被分为两个子操作:CR(回车)和LF(换行)。CR(回车)先行,启动打印头向左移动。当打印头还在移动时,LF(换行)会到达,导致纸张滚动一行。这个额外的LF(换行)字符为打印头争取到足够的时间,在下一个字符到达之前完全移到最左侧。
回看过去,文本行以CRLF结尾的传统可以追溯到20世纪50年代电传打字机的机械限制。这是一个典型的例子,说明底层实现的细节如何暴露在用户界面中。到20世纪60年代末和70年代初的Multics和Unix时代,大多数人意识到使用CRLF作为NL(新行)是没有意义的。
因此,发送单独的CR和LF字符的任务被移交给了电传打字机的设备驱动程序,因为硬件缺陷的解决应该在驱动程序层处理。计算机只需保存一个NL(新行)字符,并采用与电传打字机相同的LF(换行)代码来表示NL,这里真正的LF在实际应用中没有任何意义,因此它的数字代码被重新用于表示NL。如今,CR用Unicode编码中的U+000d作为代码点来表示,LF和NL都用U+000a表示。几乎所有现代机器都仅使用U+000a表示NL,这个意义也嵌入在大多数编程语言中,通常使用反斜杠转义符\n。
尽管如此,仍有少数机器坚持在NL之前发送CR,而U+000a的官方Unicode名称仍然是LF。此外,一些协议(如HTTP、SMTP、CSV)仍然“要求”每行以CRLF结尾。如今几乎所有软件都会接受单独的NL字符(没有前置CR)来表示行结束。你必须非常仔细地寻找,才能找到真正将U+000a解释为换行的设备或应用程序。
D. Richard Hipp直言:
“这一传统,即在每个NL(新行)之前发送无用的CR(回车),起源于旋转拨号电话时代,甚至是在集成电路发明之前。这种做法在我们的现代世界中已经没有理由继续存在了。额外的CR没有任何实际用途,只是给程序员带来不必要的麻烦,浪费带宽。”
CRLF已过时!
在这样的背景下,D. Richard Hipp发起呼吁——「所有追求简洁、和平,并希望促进人类繁荣的人,请与我一起反对使用CRLF,帮助它迅速成为历史的遗迹。」
为此,他提出了四点建议:
停止将“linefeed”(LF)作为U+000a代码点的名称。过去二十年内构建的大多数技术,以及过去半个世纪的大多数技术,已经将U+000a理解为“newline”(新行)而不是“linefeed”(换行)。虽然“linefeed”是它的历史名称,但那又有什么关系呢?在几乎所有实际应用中,它表示的是“新行”,因此请直接称它为“newline”。
停止发送不必要的CR(回车)。仅在你确实需要用新内容覆盖当前行时才使用CR。在NL(新行)之前加上CR完全是浪费带宽。除非你必须与某些顽固的系统通信,而这些系统执意停留在1950年代,否则不要在NL前加上CR。
即使某些现有协议(如HTTP、SMTP、CSV、FTP)技术上要求以CRLF作为行尾,也不要遵从。只发送NL。尽管技术上不正确,但几乎所有这些协议的实现都会接受单独的NL作为行尾标记。不要屈服于CRLF的控制。
修复那些在接收到没有前置CR的NL时表现异常或报错的软件。所有现代软件都应接受单独的U+000a字符作为有效的行尾标记。系统可能会为了向后兼容而接受CR加NL,但要求CR加NL的软件是有问题的。
在D. Richard Hipp看来,CRLF的终结早该到来了,因为它早就过时了。
带来的影响
万万没想到,此话一出,引发程序员强烈的共鸣,同时也有不少人持有不同的看法。
有开发者称,「我完全同意。这会导致无尽的混乱,尤其是在跨平台文本文件中。更不用说以编程方式解析了。」
不过,也有来自HN上的网友forrestthewoods表示:
我强烈反对这个观点。简单来说——别抱怨,自己解决。处理不同或混合的行结尾确实是个轻微的小麻烦,但并不复杂或困难。不要为了让自己轻松一点就让别人承担不必要的麻烦。接受它,继续前行。
@Animats认为,「与其呼吁,倒不如说服微软。因为这正是DOS遗留导致让这一切得以延续」。
@bmitc称:除了那些设计糟糕的Unix工具和Git,谁还会被这种问题困扰?为了适应Linux,我将编辑器配置为无论在哪个操作系统上都使用LF,并确保Git不再混淆行结尾。在处理串行协议时,我从来没有遇到过问题。
随着争议的发酵,D. Richard Hipp迫于无奈之下,于今日更新了自己的声明,他表示:
看起来(1)目前主流中的软件依赖于过时的CRLF行结尾的数量比我原先预想的还要多;
(2) 很多人并不认同我创建一个无CRLF世界的热情。唉,这让我有些失望,但现实就是如此。感谢所有愿意尝试这个想法的人,本来几乎成功了!因此,我在此撤回这项提议,并已将我所有的系统恢复为在规范要求的情况下生成CRLF。真是遗憾。
不过,这次实验带来一个意外的好处,我在Fossil和althttpd中发现并修复了一些问题,这些程序此前要求必须使用CRLF,且不允许使用单独的NL作为替代。
那么,你是否在开发中遇到过CRLF问题?
来源:
https://fossil-scm.org/home/ext/crlf-harmful.md
https://news.ycombinator.com/item?id=41830717