C 标准规定文本文件必须以换行符结尾,否则最后一个换行符后面的数据可能无法正确读取。
ISO/IEC 9899:2011 §7.21.2 流
文本流是由行组成的有序字符序列,每行
由零个或多个字符加上一个终止换行符组成。是否
最后一行需要一个终止换行符是实现定义的。人物
可能必须在输入和输出上添加、更改或删除,以符合不同的要求
在主机环境中表示文本的约定。因此,不必存在一对一的关系
流中的字符与外部字符之间的一一对应关系
表示。从文本流中读取的数据必然与数据相同
仅在满足以下条件时才先前写出到该流: 数据仅包含打印
字符和控制字符水平制表符和换行符;没有换行符
紧接着空格字符;最后一个字符是换行符。
是否在换行符之前写出空格字符
当读入是实现定义时出现。
我没想到文件末尾缺少换行符会导致出现问题bash
(或任何 Unix shell),但这似乎是可重现的问题($
是此输出中的提示):
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
它也不仅限于bash
— 科恩壳 (ksh
) and zsh
也这样做。我生活,我学习;感谢您提出问题。
如上面的代码所示,cat
命令读取整个文件。这for line in `cat $DATAFILE`
技术收集所有输出并用单个空格替换任意的空格序列(我得出的结论是文件中的每一行都不包含空格)。
在 Mac OS X 10.7.5 上测试。
POSIX 说什么?
POSIXread http://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html命令规范说:
读取实用程序应从标准输入读取一行。
默认情况下,除非-r
指定选项后, 将充当转义字符。未转义的 应保留后续字符的文字值,但 除外。如果 跟在 后面,则读取实用程序应将其解释为行继续。 和<newline>
应在将输入拆分为字段之前删除。将输入拆分为字段后,应删除所有其他未转义的 字符。
如果标准输入是终端设备并且调用 shell 是交互式的,则当读取以 结尾的输入行时,read 将提示输入续行,除非-r
选项已指定。
终止 (if any)应从输入中删除,并且结果应拆分为字段,如 shell 中参数扩展结果一样(请参阅字段拆分); [...]
请注意“(如果有)”(强调在引号中添加)!在我看来,如果没有换行符,它仍然应该读取结果。另一方面,它也说:
STDIN
标准输入应为文本文件。
然后你又回到关于不以换行符结尾的文件是否是文本文件的争论。
然而,同一页上的理由如下:
尽管标准输入必须是文本文件,因此始终以 结尾(除非它是空文件),但当-r
不使用选项可能会导致输入不以 结尾。如果输入文件的最后一行以 结尾,则会发生这种情况。正是由于这个原因,在描述中的“应从输入中删除终止(如果有)”中使用了“如果有”。这并不是放松对标准输入是文本文件的要求。
这个基本原理必然意味着文本文件应该以换行符结尾。
文本文件的 POSIX 定义是:
3.395 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_395文本文件
包含组织成零行或多行的字符的文件。这些行不包含 NUL 字符,且长度不能超过 {LINE_MAX} 个字节,包括 字符。尽管 POSIX.1-2008 不区分文本文件和二进制文件(请参阅 ISO C 标准),但许多实用程序在操作文本文件时仅产生可预测或有意义的输出。具有此类限制的标准实用程序始终在其 STDIN 或 INPUT FILES 部分中指定“文本文件”。
这并没有直接规定“以 结尾”,但确实遵循 C 标准,并且它确实表示“包含组织成零个或多个字符的文件lines当我们查看“Line”的 POSIX 定义时,它说:
3.206 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206 Line
零个或多个非 字符加上一个序列
终止 字符。
因此,根据 POSIX 定义,文件必须以终止换行符结尾,因为它由行组成,并且每行必须以终止换行符结尾。
“无终端换行符”问题的解决方案
Note 戈登·戴维森 https://stackoverflow.com/users/89817/gordon-davisson's answer https://stackoverflow.com/questions/12916352/shell-script-read-missing-last-line/12919766#12919766。一个简单的测试表明他的观察是准确的:
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
因此,他的技术是:
while read line || [ -n "$line" ]; do echo $line; done < y
or:
cat y | while read line || [ -n "$line" ]; do echo $line; done
适用于末尾没有换行符的文件(至少在我的机器上)。
我仍然惊讶地发现 shell 删除了输入的最后一段(它不能被称为一行,因为它不以换行符结尾),但 POSIX 中可能有足够的理由这样做。显然,最好确保您的文本文件确实是以换行符结尾的文本文件。