为什么文本文件应该以换行符结尾?

我想这里的每个人都熟悉所有文本文件都应该以换行符结尾的格言。我知道这条“规则”很多年了,但我一直想知道——为什么?

347537 次浏览

大概只是一些解析代码期望它在那里。

我不确定我是否会将其视为“规则”,而且它肯定不是我虔诚地遵守的东西。大多数明智的代码都知道如何逐行(任何行结尾的选择)解析文本(包括编码),无论最后一行是否有换行符。

事实上-如果你以一条新线结束:在EOL和EOF之间是否有(理论上)一个空的最后一行?

基本上有许多程序如果没有得到最终的EOL EOF,就不会正确处理文件。

GCC警告您这一点,因为它有望成为C标准的一部分。

"文件末尾没有换行符"编译器警告

我个人喜欢源代码文件末尾的新行。

它可能起源于Linux或所有UNIX系统。我记得有编译错误(如果我没弄错的话是gcc),因为源代码文件没有以空的新行结束。为什么它是这样做的,人们不禁要问。

每一行都应该以换行符结尾,包括最后一行。如果文件的最后一行没有换行符结尾,某些程序在处理它时会出现问题。

GCC发出警告不是因为它不能处理文件,而是因为它必须作为标准的一部分。

C语言标准说非空的源文件应以新行字符结尾,该字符不得紧跟在反斜杠字符之前。

由于这是一个“应”子句,我们必须发出违反此规则的诊断消息。

这是在ANSI C 1989标准的2.1.1.2部分,ISO C 1999标准的5.1.1.2部分(可能还有ISO C 1990标准)。

参考:GCC/GNU邮件存档

这可能与之间的差异有关:

  • 文本文件(每行应该以行尾结束)
  • 二进制文件(没有真正的“行”可言,必须保留文件的长度)

如果每行都以行尾结束,这就避免了,例如,连接两个文本文件会使第一个文件的最后一行运行到第二个文件的第一行。

另外,编辑器可以在加载时检查文件是否以行尾结束,将其保存在其本地选项“eol”中,并在写入文件时使用它。

几年前(2005年),许多编辑(ZDE、Eclipse、Scite……)确实“忘记”了最终的EOL,这一点不太受欢迎
不仅如此,他们还错误地将最终的EOL解释为“开始新行”,实际上开始显示另一行,就好像它已经存在一样。与在上面的编辑器中打开它相比,使用带有像vim这样行为良好的文本编辑器的“正确”文本文件,这是非常明显的。它在文件的实际最后一行下方显示了额外的一行。你会看到这样的东西:

1 first line2 middle line3 last line4

想象一下,当文件仍在由另一个进程生成时,文件正在被处理。

它可能与此有关?表示文件已准备好处理的标志。

恕我直言,这是个人风格和观点的问题。

在过去,我没有放那个换行符。保存一个字符意味着通过14.4K调制解调器更快。

后来,我放了那个换行符,这样就可以更容易地使用Shift+下箭头选择最后一行。

这起源于使用简单终端的早期。换行符字符用于触发传输数据的“刷新”。

今天,换行符字符不再需要了。当然,如果换行符不存在,许多应用程序仍然存在问题,但我认为这些应用程序中的bug。

然而,如果你有一个文本文件格式,你要求换行符,你得到简单的数据验证非常便宜:如果文件以没有换行符的行结束,你知道文件是坏的。每一行只有一个额外的字节,你可以高精度地检测损坏的文件,几乎没有CPU时间。

因为这是POSIX标准如何定义

3.206线
零个或多个非字符加上一个终止字符的序列。

因此,不以换行符结尾的行不被视为实际行。这就是为什么有些程序在处理文件的最后一行时会出现问题,如果它不是换行符结尾。

在处理终端模拟器时,此指南至少有一个硬优势:所有Unix工具都期望这种约定并使用它。例如,当将文件与cat连接时,由换行符终止的文件将与没有的文件产生不同的效果:

$ more a.txtfoo$ more b.txtbar$ more c.txtbaz$ cat {a,b,c}.txtfoobarbaz

而且,正如前面的示例还演示的那样,当在命令行上显示文件时(例如通过more),换行符终止的文件会导致正确的显示。不正确终止的文件可能会乱码(第二行)。

为了保持一致性,遵循这条规则非常有帮助-否则在处理默认Unix工具时会产生额外的工作。


换个角度思考:如果换行符没有终止行,那么使cat之类的命令有用要困难得多:如何使命令连接文件,以便

  1. 它将每个文件的开头放在一个新行上,这是您95%的时间想要的;但是
  2. 它允许合并两个文件的最后一行和第一行,如上例中b.txtc.txt之间?

当然,这是可解,但您需要使cat的使用更加复杂(通过添加位置命令行参数,例如cat a.txt --no-newline b.txt c.txt),现在命令而不是每个单独的文件控制它如何与其他文件粘贴在一起。这几乎肯定不方便。

…或者您需要引入一个特殊的哨兵字符来标记应该继续而不是终止的行。好吧,现在您遇到了与POSIX相同的情况,除了倒置(行延续而不是行终止字符)。


现在,在非POSIX兼容的系统(现在主要是Windows)上,这一点是没有意义的:文件通常不会以换行符结束,并且一行的(非正式)定义可能是例如“由换行符分隔的的文本”(注意强调)。这是完全有效的。然而,对于结构化数据(例如编程代码),这使得解析稍微复杂一点:这通常意味着必须重写解析器。如果解析器最初是以POSIX定义编写的,那么修改令牌流可能比修改解析器更容易——换句话说,在输入的末尾添加一个“人工换行符”令牌。

我一直觉得这条规则来自解析没有结束换行符的文件很困难的时代。也就是说,你最终会编写由EOL字符或EOF定义行尾的代码。假设一行以EOL结尾更简单。

然而,我相信这条规则是从需要换行符的C编译器派生出来的。正如“文件末尾没有换行符”编译器警告所指出的,#support不会添加换行符。

有些工具期望这样。例如,wc期望这样:

$ echo -n "Line not ending in a new line" | wc -l0$ echo "Line ending with a new line" | wc -l1

最后缺少换行符的文件还有一个实际的编程问题:read Bash内置(我不知道其他read实现)不能按预期工作:

printf $'foo\nbar' | while read linedoecho $linedone

这打印仅#0!原因是当read遇到最后一行时,它将内容写入$line,但返回退出代码1,因为它到达了EOF。这打破了while循环,所以我们永远不会到达echo $line部分。如果你想处理这种情况,你必须执行以下操作:

while read line || [ -n "${line-}" ]doecho $linedone < <(printf $'foo\nbar')

也就是说,如果read由于文件末尾的非空行而失败,则执行echo。自然,在这种情况下,输出中将有一个不在输入中的额外换行符。

这个答案是一个技术性的答案,而不是观点。

如果我们想成为POSIX纯粹主义者,我们将一行定义为:

零个或多个非字符加上一个终止字符的序列。

来源:https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

不完整的行为:

文件末尾的一个或多个非字符的序列。

来源:https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

一个文本文件为:

包含组织成零行或多行的字符的文件。这些行不包含NUL字符,并且长度不能超过{LINE_MAX}字节,包括字符。尽管POSIX.1-2008不区分文本文件和二进制文件(请参阅ISO C标准),但许多实用程序在对文本文件进行操作时仅产生可预测或有意义的输出。具有此类限制的标准实用程序始终在其STDIN或INPUT FILES部分指定“文本文件”。

来源:https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

字符串为:

以第一个空字节结尾并包括第一个空字节的连续字节序列。

来源:https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

由此,我们可以得出,只有当我们处理文件的线或文件作为文本文件的概念时,我们才会遇到任何类型的问题(即文本文件是零行或多行的组织,我们知道行必须以结束)。

例如:wc -l filename

wc的手册中,我们读到:

行定义为由字符分隔的字符串。

JavaScript,超文本标记语言和CSS文件的含义是什么,然后它们是文本文件?

在浏览器、现代IDE和其他前端应用程序中,在EOF跳过EOL没有问题。应用程序将正确解析文件。由于并非所有操作系统都符合POSIX标准,因此非操作系统工具(例如浏览器)根据POSIX标准(或任何操作系统级别的标准)处理文件是不切实际的。

因此,我们可以相对确信EOF的EOL在应用程序级别几乎没有负面影响-无论它是否在UNIX操作系统上运行。

在这一点上,我们可以自信地说,在客户端处理JS、超文本标记语言、CSS时,在EOF跳过EOL是安全的。实际上,我们可以声明缩小这些文件中的任何一个,不包含是安全的。

我们可以进一步说,就NodeJS而言,它也不能遵守POSIX标准,因为它可以在非POSIX兼容的环境中运行。

那么我们还剩下什么?系统级工具。

这意味着可能出现的唯一问题是那些努力将其功能与POSIX的语义学相结合的工具(例如wc中所示的行的定义)。

即便如此,并非所有shell都会自动遵守POSIX。例如,Bash不默认为POSIX行为。有一个开关可以启用它:POSIXLY_CORRECT

关于EOL被的价值的思考:https://www.rfc-editor.org/old/EOLstory.txt

停留在工具轨道上,出于所有实际意图和目的,让我们考虑一下:

让我们使用一个没有EOL的文件。在撰写本文时,本示例中的文件是一个没有EOL的缩小JavaScript。

curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.jscurl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js
$ cat x.js y.js > z.js
-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 x.js-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 y.js-rw-r--r--  1 milanadamovsky  15810 Aug 14 23:18 z.js

请注意,cat文件大小正好是其各个部分的总和。如果JavaScript文件的串联是JS文件的关注点,则更合适的关注点是用分号开始每个JavaScript文件。

正如其他人在这个线程中提到的:如果你想cat两个文件,它们的输出只变成一行而不是两行怎么办?换句话说,cat做了它应该做的事情。

catman只提到读取输入到EOF,而不是。请注意,cat-n开关也将打印出一个非终止的行(或不完全线)作为线-因为计数从1开始(根据man)。

-n对输出行进行编号,从1开始。

现在我们了解了POSIX如何定义线,这种行为变得模棱两可,或者实际上是不兼容的。

了解给定工具的用途和合规性将有助于确定使用EOL结束文件的重要性。在C、C++、Java(JAR)等中……一些标准将规定换行符的有效性——JS、超文本标记语言、CSS不存在这样的标准。

例如,可以不使用wc -l filename而使用awk '{x++}END{ print x}' filename,并且请放心,任务的成功不会受到我们可能想要处理的文件(例如,第三方库,例如缩小的JS wecurld)的危害-除非我们的意图是真正在POSIX兼容意义上计算线

结论

在现实生活中,在EOF中跳过某些文本文件(如JS,超文本标记语言和CSS)的EOL将产生负面影响的用例很少-如果有的话。如果我们依赖于存在,我们将工具的可靠性仅限于我们作者的文件,并将我们自己开放给第三方文件引入的潜在错误。

故事的寓意:工程师工具没有在EOF依赖EOL的弱点。

随意发布用例,因为它们适用于JS、超文本标记语言和CSS,我们可以在其中检查跳过EOL如何产生不利影响。

为什么(文本)文件应该以换行符结尾?

许多人也表达了这一点,因为:

  1. 许多程序表现不好,或者没有它就会失败。

  2. 即使很好地处理文件的程序也没有结尾'\n',该工具的功能可能无法满足用户的期望-在这个角落情况下可能不清楚。

  3. 程序很少禁止最终'\n'(我不知道任何)。


这就引出了下一个问题:

代码应该如何处理没有换行符的文本文件?

  1. 最重要的-不要编写假定文本文件以换行符结尾的代码假设文件符合某种格式会导致数据损坏、黑客攻击和崩溃。示例:

    // Bad codewhile (fgets(buf, sizeof buf, instream)) {// What happens if there is no \n, buf[] is truncated leading to who knows whatbuf[strlen(buf) - 1] = '\0';  // attempt to rid trailing \n...}
  2. If the final trailing '\n' is needed, alert the user to its absence and the action taken. IOWs, validate the file's format. Note: This may include a limit to the maximum line length, character encoding, etc.

  3. Define clearly, document, the code's handling of a missing final '\n'.

  4. Do not, as possible, generate a file the lacks the ending '\n'.

除了上述实际原因之外,如果Unix的创始人(Thompson、Ritchie等人)或他们的Multics前辈意识到使用行终止符而不是行分隔符有一个理论上的理由,我也不会感到惊讶:使用行终止符,您可以对所有可能的行文件进行编码。使用行分隔符,零行文件和包含单个空行的文件没有区别;它们都被编码为包含零个字符的文件。

所以,原因是:

  1. 因为这就是POSIX定义它的方式。
  2. 因为有些工具期望它或没有它的“行为不端”。例如,如果wc -l不以换行符结尾,它将不会计算最后的“行”。
  3. 因为它简单方便。在Unix上,cat可以正常工作,并且没有复杂的工作。它只是复制每个文件的字节,不需要任何解释。我认为没有一个DOS等同于cat。使用copy a+b c最终会合并文件a的最后一行和文件b的第一行。
  4. 因为一个零行的文件(或流)可以与一个空行的文件区分开来。

我自己也想了很多年了。但今天我找到了一个很好的理由。

想象一个每行都有记录的文件(例如:CSV文件)。并且计算机正在文件末尾写入记录。但它突然崩溃了。哎呀,最后一行完成了吗?(不是一个好情况)

但是如果我们总是终止最后一行,那么我们就会知道(只需检查最后一行是否终止)。否则,我们可能每次都要丢弃最后一行,只是为了安全起见。

一个单独的用例:当您的文本文件受版本控制时,提交卫生。

如果将内容添加到文件末尾,则之前最后一行的行将被编辑为包含换行符。这意味着blameing文件以找出该行上次编辑的时间将显示换行符添加,而不是您实际希望看到的之前的提交。

(该示例特定于git,但相同的方法也适用于其他版本控制系统。)

这里很晚了,但我刚刚在文件处理中遇到了一个bug,这是因为文件没有以空换行符结束。我们正在处理带有sed的文本文件,sed省略了输出中的最后一行,这导致无效的json结构并将进程的其余部分发送到失败状态。

我们所做的只是:

有一个示例文件说:foo.txt里面有一些json的内容。

[{someProp: value},{someProp: value}] <-- No newline here

该文件是在寡妇机器中创建的,窗口脚本正在使用PowerShell命令处理该文件。一切都好。

当我们使用sed命令sed 's|value|newValue|g' foo.txt > foo.txt.tmp处理同一个文件时

新生成的文件是

[{someProp: value},{someProp: value

和繁荣,它失败了其余的进程,因为无效的JSON。

因此,用空的新行结束文件始终是一个很好的做法。

为什么文本文件应该以换行符结尾?

因为这是最明智的选择。

取一个包含以下内容的文件,

one\ntwo\nthree

其中\n表示换行符,在Windows上是\r\n,返回字符后跟换行符,因为它太酷了,对吧?

这个文件有多少行?Windows说3,我们说3,POSIX(Linux)说文件是瘫痪的,因为它的末尾应该有一个\n

无论如何,你会说它的最后一行是什么?我想每个人都同意three是文件的最后一行,但POSIX说这是一个残缺的行。

它的第二行是什么?哦,这里我们有第一个强分离

  • Windows表示two,因为文件是“由换行符分隔的行”(wth?);
  • POSIX说two\n,并补充说这是一个真实的,诚实的线。

那么选择Windows的后果是什么呢?很简单:

你不能说一个文件是由行组成的

为什么?尝试从上一个文件中取出最后一行并复制几次……你得到了什么?这:

one\ntwo\nthreethreethreethree

尝试,相反,交换第二行和第三行……你得到这个:

one\nthreetwo\n

因此

你必须说一个文本文件是行和\n的交替,它以一行开始,以一行结束

这是相当的一口,对不对?

你想要另一个奇怪的结果吗?

你必须接受一个空文件(0字节,实际上是0位)是一个单行文件,神奇的是,总是因为它们在微软很酷。

这太疯狂了,你不觉得吗?

POSIX选择的后果是什么?

顶部的文件只是有点残废,我们需要一些黑客来处理它。

是认真的

在前面的文本中,我是挑衅性的,因为处理最后缺少\n的文本文件迫使你用特设滴答/黑客来处理它们。你总是需要一个if/else的地方来使事情正常工作,其中处理瘫痪线路的分支只处理瘫痪线路,所有其他线路都拿走另一个分支。这有点种族主义,不是吗?

我的结论

我赞成POSIX定义一行,原因如下:

  • 一个文件自然被认为是一系列行
  • 一行不应该是这样或那样的取决于它在文件中的位置
  • 空文件不是单行文件,来吧!
  • 你不应该被迫在你的代码中进行黑客攻击

是的,Windows确实鼓励您省略尾随的\r\n。如果您想要下面的两行文件,则必须省略尾随的\r\n,否则文本编辑器将其显示为3行文件:输入图片描述