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


很可能只是一些解析代码希望它在那里。

我不确定我是否会认为这是一条“规则”,而且这肯定不是我虔诚地遵守的。最明智的代码将知道如何逐行解析文本(包括编码)(任何行结尾的选择),最后一行是否有换行符。

的确,如果你以一条新的线结束:EOL和EOF之间(理论上)是否有一条空的最终线?一个值得思考的。。。


基本上,如果没有得到最终EOL EOF,许多程序将无法正确处理文件。

GCC警告您这一点,因为它是C标准的一部分。(第5.1.1.2节明显)

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


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

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


每一行都应该以换行符结尾,包括最后一行。有些程序在处理文件的最后一行时遇到问题,如果它不是换行符。

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

C语言标准说非空的源文件应以换行符结尾,换行符前不得紧跟反斜杠字符。由于这是一个“应”条款,我们必须发出一条违反此规则的诊断信息。这在ANSI C 1989标准第2.1.1.2节中。ISO C 1999标准(可能还有ISO C 1990标准)第5.1.1.2节。

参考:GCC/GNU邮件存档。


这可能与以下两者之间的差异有关:

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

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

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

几年前(2005年),许多编辑(ZDE、Eclipse、Scite…)确实“忘记”了最后的EOL,这并不是很受欢迎。不仅如此,他们还错误地将最后的EOL解释为“开始一行”,实际上开始显示另一行,就好像它已经存在一样。与在上述编辑器之一中打开文本文件相比,这在“适当”的文本文件中是非常明显的,该文件具有良好的文本编辑器(如vim)。它在文件的最后一行下面显示了一行。你会看到这样的情况:

1 first line
2 middle line
3 last line
4

假设文件正在被处理,而文件仍由另一个进程生成。

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


天啊,这是个人风格和观点的问题。

在过去,我没有写那句新语。保存的字符意味着14.4K调制解调器的速度更快。

稍后,我放置了换行符,以便使用shift+向下箭头更容易选择最后一行。


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

今天,不再需要换行符。当然,如果没有换行符,许多应用程序仍然存在问题,但我认为这是这些应用程序中的一个错误。

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


因为POSIX标准就是这样定义一行的:

3.206线路一个由零个或多个非<换行符>字符加上一个终止<换行符]字符组成的序列。

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

在使用终端仿真器时,该指南至少有一个硬优势:所有Unix工具都希望使用此约定并使用它。例如,当使用cat连接文件时,以换行符结尾的文件将具有不同于不使用的效果:

$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz

而且,如前一个示例所示,当在命令行上显示文件时(例如,通过more),换行的文件会导致正确的显示。未正确终止的文件可能会乱码(第二行)。

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


换一种方式思考:如果行没有以换行符结尾,那么让cat之类的命令变得有用就要困难得多了:如何创建一个连接文件的命令,以便

它将每个文件的开头放在一个新行上,这是您95%的时间所希望的;但是它允许合并两个文件的最后一行和第一行,就像上面的例子中的b.txt和c.txt?

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

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


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


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

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


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

$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1

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

printf $'foo\nbar' | while read line
do
    echo $line
done

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

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

也就是说,如果由于文件末尾的非空行导致读取失败,则执行回显。当然,在这种情况下,输出中将有一个额外的换行符,而输入中没有。


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

如果我们想成为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

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

大小写:wc-l文件名。

从wc手册中,我们了解到:

行定义为一个由<newline>字符分隔的字符串。

如果JavaScript、HTML和CSS文件是文本文件,那么它们的含义是什么?

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

因此,我们可以相对确信,EOF的EOL在应用程序级别几乎不会产生负面影响——无论它是否在UNIX OS上运行。

此时,我们可以自信地说,在客户端处理JS、HTML和CSS时,在EOF中跳过EOL是安全的。实际上,我们可以声明,缩小其中任何一个不包含<newline>的文件都是安全的。

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

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

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

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

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

为了所有实际意图和目的,让我们继续走在工具轨道上,考虑一下:

让我们处理一个没有EOL的文件。至此,本例中的文件是一个缩小的JavaScript,没有EOL。

curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
curl 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文件。

正如其他人在本主题中提到的:如果你想抓取两个输出仅为一行而不是两行的文件,该怎么办?换句话说,猫做它应该做的事。

猫的男人只提到阅读输入到EOF,而不是<newline>。请注意,cat的-n开关还将打印出一个非<换行符>终止的行(或不完整的行)作为一行,即计数从1开始(根据man的说法)

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

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

了解给定工具的用途和合规性将有助于确定使用EOL结束文件的重要性。在C、C++、Java(JARs)等中,一些标准会规定一个新的有效性标准——JS、HTML、CSS不存在这样的标准。

例如,可以不使用wc-l文件名,而是使用awk“{x++}END{print x}”文件名,并确保任务的成功不会受到我们可能要处理的文件(例如,第三方库,例如我们固化的精简JS)的影响,除非我们的目的是真正按照POSIX兼容的方式计算行数。

结论

在现实生活中,对于某些文本文件(如JS、HTML和CSS),在EOF时跳过EOL会产生负面影响(如果有的话)。如果我们依赖于<newline>的存在,我们将工具的可靠性限制在我们编写的文件上,并将自己暴露在第三方文件引入的潜在错误中。

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

请随意发布用例,因为它们适用于JS、HTML和CSS,我们可以在其中检查跳过EOL是如何产生不利影响的。


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

正如许多人所表达的,因为:

许多程序运行不好,或者没有它就会失败。即使能很好地处理文件的程序缺少结尾“\n”,该工具的功能也可能无法满足用户的期望——在这种情况下,这一点可能不清楚。程序很少禁止最后的“\n”(我不知道有)。


然而,这引出了下一个问题:

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

最重要的是,不要编写假设文本文件以换行符结尾的代码。假设文件符合某种格式会导致数据损坏、黑客攻击和崩溃。例子://错误的代码while(fgets(buf,buf大小,instream)){//如果没有\n,buf[]被截断,会发生什么buf[strlen(buf)-1]=“\0”;//尝试删除尾部\n...}如果需要最后一个结尾“\n”,请提醒用户该结尾不存在以及所采取的操作。IOW,验证文件的格式。注意:这可能包括对最大行长度、字符编码等的限制。清楚地定义,文档,代码对缺少final“\n”的处理。尽可能不要生成缺少结尾“\n”的文件。


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

因此,原因如下:

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


多年来,我自己也在想这个问题。但我今天遇到了一个很好的理由。

想象一个每行都有记录的文件(例如:CSV文件)。电脑在文件末尾写记录。但它突然坠毁了。天哪,最后一行完成了吗?(情况不太好)

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


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

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

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


现在已经很晚了,但我在文件处理中遇到了一个错误,这是因为文件没有以空换行结尾。我们使用sed处理文本文件,sed省略了输出的最后一行,这导致无效的json结构,并将流程的其余部分发送到失败状态。

我们所做的就是:

有一个示例文件,比如:foo.txt,其中包含一些json内容。

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

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

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

新生成的文件是

[{
    someProp: value
},
{
    someProp: value

和boom,由于无效的JSON,它使其余过程失败。

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


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

因为这是最明智的选择。

获取包含以下内容的文件:,

one\n
two\n
three

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

这个文件有多少行?Windows说3,我们说3,POSIX(Linux)说文件是残缺的,因为文件末尾应该有一个。

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

第二行是什么?哦,这里有第一个强烈的分离:

Windows说两个是因为文件是“用换行符分隔的行”(wth?);POSIX说2,并补充说这是一条真实、诚实的路线。

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

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

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

one\n
two\n
threethreethreethree

相反,尝试交换第二行和第三行。。。你会发现:

one\n
threetwo\n

因此

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

这真是一口,对吧?

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

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

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

POSIX选择的后果是什么?

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

是认真的

在前面的文本中,我是挑衅性的,因为处理缺少结尾的文本文件会迫使您使用特殊的滴答声/黑客来处理它们。你总是需要一个if/else来让事情运转起来,其中处理残缺行的分支只处理残缺行,所有其他行都采用另一个分支。这有点种族主义,不是吗?

我的结论

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

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


是的,Windows确实鼓励您省略后面的\r\n。如果需要下面的两行文件,则必须省略尾部,否则文本编辑器会将其显示为三行文件: