我假设这里的每个人都熟悉所有文本文件都应该以换行符结尾的格言。我已经知道这个“规则”很多年了,但我一直想知道——为什么?
19 回答
因为这就是POSIX 标准定义一行的方式:
- 3.206线
- 零个或多个非 <newline> 字符加上终止 <newline> 字符的序列。
因此,不以换行符结尾的行不被视为实际行。这就是为什么某些程序在处理文件的最后一行时遇到问题的原因,如果它不是换行符终止的话。
在终端仿真器上工作时,该指南至少有一个硬性优势:所有 Unix 工具都期望这个约定并使用它。例如,当使用 连接文件时cat
,由换行符终止的文件将与没有的文件具有不同的效果:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
而且,正如前面的示例还演示的那样,当在命令行上显示文件时(例如 via more
),换行符终止的文件会导致正确的显示。不正确终止的文件可能会出现乱码(第二行)。
为了保持一致性,遵循这条规则非常有帮助——否则在处理默认的 Unix 工具时会产生额外的工作。
换个角度想一想:如果行不被换行符终止,那么使命令cat
变得有用就更难了:你如何创建一个命令来连接文件,这样
- 它将每个文件的开头放在一个新行上,这是您 95% 的时间想要的;但
- 它允许合并两个文件的最后一行和第一行,如上面的示例中
b.txt
和c.txt
?
当然这是可以解决的,但是您需要使使用cat
更复杂(通过添加位置命令行参数,例如cat a.txt --no-newline b.txt c.txt
),现在命令而不是每个单独的文件控制它如何与其他文件一起粘贴。这几乎肯定不方便。
... 或者您需要引入一个特殊的标记字符来标记应该继续而不是终止的行。好吧,现在您遇到了与 POSIX 相同的情况,除了反转(行继续而不是行终止字符)。
现在,在不符合 POSIX的系统(现在主要是 Windows)上,这一点没有实际意义:文件通常不以换行符结尾,并且行的(非正式)定义可能例如是“由换行符分隔的文本” (注意重点)。这是完全有效的。然而,对于结构化数据(例如编程代码),它使解析变得更加复杂:这通常意味着必须重写解析器。如果一个解析器最初是在考虑 POSIX 定义的情况下编写的,那么修改令牌流可能比修改解析器更容易——换句话说,在输入的末尾添加一个“人工换行”令牌。
每行都应以换行符结束,包括最后一行。如果文件不是换行符终止,则某些程序在处理文件的最后一行时会出现问题。
GCC 警告它不是因为它不能处理文件,而是因为它必须作为标准的一部分。
C 语言标准规定非空的源文件应以换行符结尾,该换行符之前不应紧跟反斜杠字符。
由于这是一个“shall”子句,因此我们必须针对违反此规则的行为发出诊断消息。
这在 ANSI C 1989 标准的第 2.1.1.2 节中。ISO C 1999 标准的第 5.1.1.2 节(可能还有 ISO C 1990 标准)。
参考:GCC/GNU 邮件存档。
这个答案是对技术答案而不是意见的尝试。
如果我们想成为 POSIX 纯粹主义者,我们将一行定义为:
零个或多个非 <newline> 字符加上终止 <newline> 字符的序列。
来源:https ://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206
不完整的行为:
文件末尾的一个或多个非 <newline> 字符序列。
来源:https ://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195
一个文本文件为:
包含组织成零行或多行的字符的文件。这些行不包含 NUL 字符,长度不能超过 {LINE_MAX} 个字节,包括 <newline> 字符。尽管 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 filename
。
从wc
's 手册中我们读到:
行定义为由 <newline> 字符分隔的字符串。
那么对于 JavaScript、HTML 和 CSS 文件来说它们是文本 文件有什么影响呢?
在浏览器、现代 IDE 和其他前端应用程序中,在 EOF 跳过 EOL 没有问题。应用程序将正确解析文件。它必须因为并非所有操作系统都符合 POSIX 标准,因此非操作系统工具(例如浏览器)根据 POSIX 标准(或任何操作系统级标准)处理文件是不切实际的。
因此,我们可以相对确信 EOF 的 EOL 对应用程序级别几乎没有负面影响——无论它是否在 UNIX 操作系统上运行。
在这一点上,我们可以自信地说,在客户端处理 JS、HTML、CSS 时,在 EOF 处跳过 EOL 是安全的。实际上,我们可以说缩小这些文件中的任何一个,不包含 <newline> 是安全的。
我们可以更进一步说,就 NodeJS 而言,它也不能遵守 POSIX 标准,因为它可以在不符合 POSIX 的环境中运行。
那我们还剩下什么?系统级工具。
这意味着可能出现的唯一问题是那些努力将其功能与 POSIX 语义相结合的工具(例如,如图所示的行定义wc
)。
即便如此,并不是所有的 shell 都会自动遵守 POSIX。例如,Bash 不默认为 POSIX 行为。有一个开关可以启用它:POSIXLY_CORRECT
。
关于 EOL 的价值是 <newline> 的思考: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.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 文件都以分号开头。
正如该线程中的其他人所提到的:如果您想要cat
两个文件的输出只是一行而不是两行怎么办?换句话说,cat
做它应该做的事情。
man
仅提到cat
读取输入到 EOF,而不是 <newline> 。请注意,-n
开关cat
还将打印出非 <newline> 终止的行(或不完整的行)作为一行- 因为计数从1开始(根据man
.)
-n 为输出行编号,从 1 开始。
现在我们了解了 POSIX 如何定义line,这种行为变得模棱两可,或者说真的不合规。
了解给定工具的用途和合规性将有助于确定以 EOL 结束文件的重要性。在 C、C++、Java (JAR) 等中......一些标准将规定换行符的有效性 - JS、HTML、CSS 不存在这样的标准。
例如,不要使用wc -l filename
one could do awk '{x++}END{ print x}' filename
,并请放心,任务的成功不会受到我们可能想要处理但不是我们编写的文件的危害(例如,第三方库,例如我们curl
d 的缩小 JS)——除非我们的真正的目的是在符合 POSIX 的意义上计算行数。
结论
对于某些文本文件(如 JS、HTML 和 CSS)在 EOF 跳过 EOL 会产生负面影响(如果有的话)的实际用例将非常少。如果我们依赖 <newline> 的存在,我们将工具的可靠性限制在我们创作的文件中,并对第三方文件引入的潜在错误敞开大门。
故事的寓意:工程师工具不具有在 EOF 依赖 EOL 的弱点。
随意发布适用于 JS、HTML 和 CSS 的用例,我们可以在其中检查跳过 EOL 如何产生不利影响。
这可能与以下之间的差异有关:
- 文本文件(每一行都应该以行尾结尾)
- 二进制文件(没有真正的“行”可言,必须保留文件的长度)
如果每一行都以行尾结尾,例如,这可以避免连接两个文本文件会使第一个文件的最后一行变成第二个文件的第一行。
另外,编辑器可以在加载时检查文件是否以行尾结尾,将其保存在其本地选项“eol”中,并在写入文件时使用它。
几年前(2005 年),许多编辑(ZDE、Eclipse、Scite,...)确实“忘记”了最后的 EOL,这并不是很受欢迎。
不仅如此,他们还错误地将最终的 EOL 解释为“开始新行”,并且实际上开始显示另一行,就好像它已经存在一样。
与在上述编辑器之一中打开它相比,使用像 vim 这样表现良好的文本编辑器的“正确”文本文件非常明显。它在文件的实际最后一行下方显示了一个额外的行。你会看到这样的东西:
1 first line
2 middle line
3 last line
4
一些工具期望这一点。例如,wc
期望这样:
$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
一个单独的用例:当您的文本文件受版本控制时(在这种情况下,特别是在 git 下,尽管它也适用于其他文件)。如果将内容添加到文件的末尾,则之前最后一行的行将被编辑为包含换行符。这意味着blame
对文件进行查找以找出该行最后一次编辑的时间将显示文本添加,而不是您实际想要查看的之前的提交。
我自己多年来一直想知道这一点。但我今天遇到了一个很好的理由。
想象一个每行都有一条记录的文件(例如:CSV 文件)。并且计算机正在文件末尾写入记录。但它突然崩溃了。哎呀,最后一行完成了吗?(不是一个好的情况)
但是如果我们总是终止最后一行,那么我们就会知道(只需检查最后一行是否终止)。否则,为了安全起见,我们可能每次都必须丢弃最后一行。
这源于使用简单终端的早期阶段。换行符用于触发传输数据的“刷新”。
今天,换行符不再需要了。当然,如果没有换行符,许多应用程序仍然存在问题,但我认为这是这些应用程序中的一个错误。
但是,如果您有一个需要换行符的文本文件格式,那么您可以非常便宜地获得简单的数据验证:如果文件以没有换行符的行结尾,您就知道文件已损坏。每行只需一个额外的字节,您就可以高精度地检测损坏的文件,并且几乎不需要 CPU 时间。
除了上述实际原因之外,如果 Unix 的创始人(Thompson、Ritchie 等人)或他们的 Multics 前辈意识到使用行终止符而不是行分隔符有理论上的原因,我不会感到惊讶:终止符,您可以对所有可能的行文件进行编码。使用行分隔符,零行文件和包含单个空行的文件没有区别;它们都被编码为包含零个字符的文件。
所以,原因是:
- 因为这就是 POSIX 定义它的方式。
- 因为一些工具期望它或没有它“行为不端”。例如,
wc -l
如果最后的“行”不以换行符结尾,则不会计算它。 - 因为它简单方便。在 Unix 上,
cat
它可以正常工作并且没有复杂性。它只是复制每个文件的字节,不需要任何解释。我认为没有 DOS 等效于cat
. 使用copy a+b c
最终会将 file 的最后一行与 filea
的第一行合并b
。 - 因为零行的文件(或流)可以与一个空行的文件区分开来。
最后缺少换行符的文件还有一个实际的编程问题: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')
也就是说,echo
如果read
由于文件末尾的非空行而失败,请执行此操作。自然地,在这种情况下,输出中会有一个额外的换行符,而输入中没有。
大概只是一些解析代码期望它在那里。
我不确定我是否会将其视为“规则”,而且这当然不是我虔诚地遵守的。大多数明智的代码都知道如何逐行解析文本(包括编码)(任何行尾选择),最后一行有或没有换行符。
确实-如果您以新行结尾:(理论上)EOL 和 EOF 之间是否有空的最后一行?一个值得深思...
为什么(文本)文件应该以换行符结尾?
许多人也表示,因为:
许多程序表现不佳,或者没有它就会失败。
即使可以很好地处理文件的程序也缺少结尾
'\n'
,该工具的功能也可能无法满足用户的期望——在这种极端情况下可能不清楚。程序很少不允许final
'\n'
(我不知道)。
然而这引出了下一个问题:
对于没有换行符的文本文件,代码应该怎么做?
最重要的 -不要编写假定文本文件以换行符结尾的代码。 假设文件符合某种格式会导致数据损坏、黑客攻击和崩溃。例子:
// Bad code while (fgets(buf, sizeof buf, instream)) { // What happens if there is no \n, buf[] is truncated leading to who knows what buf[strlen(buf) - 1] = '\0'; // attempt to rid trailing \n ... }
如果需要最后的尾随
'\n'
,请提醒用户它的缺失和所采取的措施。IOW,验证文件的格式。注意:这可能包括对最大行长度、字符编码等的限制。清楚地定义,记录,代码对缺少的 final 的处理
'\n'
。尽可能不要生成缺少结尾的文件
'\n'
。
这里已经很晚了,但我刚刚在文件处理中遇到了一个错误,这是因为文件没有以空换行符结尾。我们正在处理文本文件,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\n
two\n
three
where\n
表示换行符,在 Windows 上是\r\n
,返回字符后跟换行符,因为它很酷,对吧?
这个文件有多少行?Windows 说 3,我们说 3,POSIX (Linux) 说文件已损坏,因为文件\n
末尾应该有 a。
无论如何,你会说它的最后一行是什么?我想有人同意这three
是文件的最后一行,但 POSIX 说这是一条残缺的行。
它的第二行是什么?哦,这里我们有第一个强分离:
- Windows 说
two
因为文件是“由换行符分隔的行”(wth?); - POSIX says
two\n
, adding that that's a true, honest line.
What's the consequence of Windows choice, then? Simple:
You cannot say that a file is made up of lines
Why? Try to take the last line from the previous file and replicate it a few times... What you get? This:
one\n
two\n
threethreethreethree
Try, instead, to swap second and third line... And you get this:
one\n
threetwo\n
Therefore
You must say that a text file is an alternation of lines and \n
s, which starts with a line and ends with a line
which is quite a mouthful, right?
And you want another strange consequence?
You must accept that an empty file (0 bits) is a one-line file, magically, always because they are cool at Microsoft
Which is quite a crazyness, don't you think?
What is the consequence of POSIX choice?
That the file on the top is just a bit crippled, and we need some hack to deal with it.
认真
在前面的文本中,我是在挑衅,因为处理\n
末尾缺少 的文本文件会迫使您使用临时记号/黑客来对待它们。您总是需要一个if
/else
某处才能使事情正常进行,其中处理残废线路的分支仅处理残废线路,所有其他线路都使用另一个分支。这有点种族主义,不是吗?
我的结论
我赞成使用 POSIX 定义一行,原因如下:
- 文件自然地被认为是一系列行
- 一行不应该是一回事,具体取决于它在文件中的位置
- 空文件不是一行文件,加油!
- 你不应该被迫在你的代码中进行黑客攻击
我一直觉得这条规则来自于解析没有结束换行符的文件很困难的日子。也就是说,您最终会编写代码,其中行尾由 EOL 字符或 EOF 定义。假设一行以 EOL 结尾更简单。
但是我相信该规则源自需要换行符的 C 编译器。正如在“文件末尾没有换行符”编译器警告中指出的那样,#include 不会添加换行符。
想象一下,当文件仍在由另一个进程生成时,正在处理该文件。
可能跟这个有关?指示文件已准备好进行处理的标志。
我个人喜欢源代码文件末尾的新行。
它可能起源于 Linux 或所有 UNIX 系统。我记得有编译错误(如果我没记错的话是 gcc),因为源代码文件没有以空的新行结尾。为什么它是这样制作的,值得怀疑。
恕我直言,这是个人风格和观点的问题。
在过去,我没有放那个换行符。保存一个字符意味着通过 14.4K 调制解调器的速度更快。
后来,我放了那个换行符,以便使用 shift+downarrow 更容易选择最后一行。