我假设这里的每个人都熟悉所有文本文件应以换行符结尾的格言.多年来我一直都知道这个"规则",但我一直在想 - 为什么?
因为这是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定义编写的,那么修改令牌流而不是解析器可能更容易 - 换句话说,在输入的末尾添加"人工换行"令牌.
每一行都应以换行符结尾,包括最后一行.如果某个程序不是换行符,则会在处理文件的最后一行时遇到问题.
GCC警告它不是因为它无法处理文件,而是因为它必须作为标准的一部分.
C语言标准说一个非空的源文件应以换行符结尾,换行符前面不应该有反斜杠字符.
由于这是一个"shall"子句,我们必须发出违反此规则的诊断消息.
这在ANSI C 1989标准的2.1.1.2节中.ISO C 1999标准的5.1.1.2节(也可能是ISO C 1990标准).
参考:GCC/GNU邮件存档.
这个答案是尝试技术答案而不是意见.
如果我们想成为POSIX纯粹主义者,我们将一行定义为:
一系列零个或多个非
字符加上一个终止 字符.
资料来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206
一条不完整的行:
文件末尾的一个或多个非
字符的序列.
资料来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195
文本文件为:
包含组织为零行或多行的字符的文件.这些行不包含NUL字符,长度不能超过{LINE_MAX}个字节,包括
字符.尽管POSIX.1-2008不区分文本文件和二进制文件(请参阅ISO C标准),但许多实用程序在操作文本文件时仅产生可预测或有意义的输出.具有此类限制的标准实用程序始终在其STDIN或INPUT FILES部分中指定"文本文件".
资料来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397
字符串为:
由第一个空字节终止并包括第一个空字节的连续字节序列.
资料来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396
从这以后,我们可以得出的唯一的一次,我们将有可能遇到什么类型的问题是,如果我们处理一个概念行的文件或文件为文本文件(是一个文本文件是零的组织或更多行,我们知道的行必须以
例证:wc -l filename
.
从wc
我们的手册中我们读到:
行被定义为由
字符分隔的字符串.
对JavaScript,HTML和CSS文件有什么影响,那么它们是文本 文件?
在浏览器,现代IDE和其他前端应用程序中,在EOF中跳过EOL没有问题.应用程序将正确解析文件.由于并非所有操作系统都符合POSIX标准,因此非OS工具(例如浏览器)根据POSIX标准(或任何操作系统级标准)处理文件是不切实际的.
因此,我们可以相对确信EOF的EOL在应用程序级别几乎没有负面影响 - 无论它是否在UNIX OS上运行.
在这一点上,我们可以自信地说,在客户端处理JS,HTML,CSS时,在EOF上跳过EOL是安全的.实际上,我们可以声明缩小其中任何一个文件,不包含
我们可以更进一步说,就NodeJS而言,它也不能遵守POSIX标准,因为它可以在非POSIX兼容环境中运行.
那我们还剩下什么?系统级工具.
这意味着可能出现的唯一问题是工具努力将其功能与POSIX的语义相结合(例如,如图所示定义一条线wc
).
即便如此,并非所有shell都会自动粘贴到POSIX上.例如,Bash不默认为POSIX行为.有一个开关启用它:POSIXLY_CORRECT
.
关于EOL价值的思考的食物是
保持工具轨道,出于所有实际意图和目的,让我们考虑一下:
让我们使用没有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,不<换行符>.请注意,-n
切换也cat
将打印出非<换行>终止行(或不完整行)作为一行 - 计数从1开始(根据man
.)
-n编号输出行,从1开始.
现在我们已经理解了POSIX如何定义一条线,这种行为变得模棱两可,或者真的不合规.
了解给定工具的目的和合规性将有助于确定使用EOL结束文件的重要性.在C,C++,Java(JAR)等中......一些标准将规定有效性的换行符 - JS,HTML,CSS没有这样的标准.
例如,不要使用wc -l filename
一个可以做的awk '{x++}END{ print x}' filename
,并且放心,任务的成功不会受到我们可能想要处理的文件的危害,我们没有写入(例如第三方库,例如缩小的JS curl
) - 除非我们意图是真正计算符合POSIX标准的行.
结论
现实生活中的用例非常少,在EOF中为某些文本文件(如JS,HTML和CSS)跳过EOL会产生负面影响 - 如果有的话.如果我们依赖
故事的道德:在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
基本上有许多程序如果没有得到最终的EOL EOF,将无法正确处理文件.
海湾合作委员会警告你,因为它是C标准的一部分.(显然是第5.1.1.2节)
"文件末尾没有换行符"编译器警告
一个单独的用例:当你的文本文件受版本控制时(在这种情况下特别是在git下,虽然它也适用于其他人).如果将内容添加到文件末尾,则之前最后一行的行将被编辑为包含换行符.这意味着blame
查找文件以找出上次编辑该行的时间将显示文本添加,而不是您实际想要查看之前的提交.
这源于使用简单终端的早期阶段.换行符char用于触发传输数据的"刷新".
今天,不再需要newline char.当然,如果换行不存在,许多应用程序仍有问题,但我认为这些应用程序中存在错误.
但是,如果你有一个需要换行符的文本文件格式,那么你可以非常便宜地获得简单的数据验证:如果文件以一行最后没有换行的行结束,你知道该文件已损坏.每行只有一个额外字节,您可以高精度地检测损坏的文件,几乎没有CPU时间.
除了上述实际原因之外,如果Unix的发起者(Thompson,Ritchie等人)或他们的Multics前辈意识到理论上有理由使用行终止符而不是行分隔符,那就不会让我感到惊讶:终结器,您可以编码所有可能的行文件.对于行分隔符,零行文件和包含单个空行的文件之间没有区别; 它们都被编码为包含零个字符的文件.
所以,原因是:
因为这是POSIX定义它的方式.
因为有些工具在没有它的情况下期望它或"行为不端".例如,wc -l
如果不以换行结束,则不会计算最终的"行".
因为它简单方便.在Unix上,cat
只是工作,它没有复杂的工作.它只是复制每个文件的字节,而不需要解释.我不认为有一个DOS相当于cat
.使用copy a+b c
将最终将文件的最后一行a
与第一行文件合并b
.
因为可以将零行的文件(或流)与一个空行的文件区分开来.
大概只是一些解析代码期望它存在.
我不确定我会认为它是一个"规则",它肯定不是我坚持宗教的东西.最明智的代码将知道如何逐行解析文本(包括编码)(任何行结尾的选择),最后一行有或没有换行符.
确实 - 如果你以一条新线结束:理论上是否存在EOL和EOF之间的空白终点线?一个思考......
还有一个实际的编程问题,最后缺少换行的文件: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
由于文件末尾的非空行而失败.当然,在这种情况下,输出中将有一个额外的换行符不在输入中.
多年来我一直在想这个.但我今天遇到了一个很好的理由.
想象一下每行都有记录的文件(例如:CSV文件).并且计算机正在文件末尾写入记录.但它突然崩溃了.Gee是最后一行完成的?(不是很好的情况)
但是如果我们总是终止最后一行,那么我们就知道了(只需检查最后一行是否终止).否则我们可能不得不每次丢弃最后一行,只是为了安全起见.
为什么(文本)文件以换行符结尾?
许多人表达了,因为:
许多程序表现不佳,没有程序就会失败.
即使是处理文件的程序也没有结束'\n'
,该工具的功能可能无法满足用户的期望 - 在这个角落的案例中可能不清楚.
程序很少不允许最终'\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'
需要最终跟踪,请提醒用户缺席并采取措施.IOWs,验证文件的格式.注意:这可能包括最大行长度,字符编码等的限制.
明确定义,文档,代码处理缺失的决赛'\n'
.
尽量不要生成缺少结尾的文件'\n'
.