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
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done
abc
def
ghi
xxx
$
它也不仅限于bash
-Korn shell(ksh
),其zsh
行为也是如此。我生活,我学习;感谢您提出这个问题。
如上面的代码所示,该cat
命令读取整个文件。该for line in `cat $DATAFILE`
技术收集所有输出,并用单个空格替换空白序列(我得出结论,文件中的每一行均不包含空格)。
在Mac OS X 10.7.5上测试。
POSIX说什么?
POSIXread
命令规范说:
读取实用程序应从标准输入读取一行。
默认情况下,除非-r
指定了该选项,否则<backslash>将充当转义字符。未经转义的<反斜杠>应保留以下字符的字面值,但<换行符>除外。如果<newline>在<backslash>之后,则read实用程序应将此解释为行继续。<backslash>和<newline>
必须在将输入拆分为字段之前删除。将输入拆分为多个字段后,应删除所有其他未转义的<反斜杠>字符。
如果标准输入是终端设备,并且调用外壳是交互式的,则除非-r
指定了该选项,否则read在读取以<backslash> <newline>结尾的输入行时将提示输入连续行。
终止的<newline> (如果有的话)应从输入中删除,结果应像在shell中一样拆分为字段,以扩展参数(请参见字段拆分);[...]
请注意,“(如果有)”(加引号)!在我看来,如果没有换行符,它仍应读取结果。另一方面,它也说:
标准输入
标准输入应为文本文件。
然后回到关于不以换行符结尾的文件是否为文本文件的争论。
但是,同一页面上的基本原理记录了:
尽管标准输入必须是文本文件,因此将始终以<newline>结尾(除非它是空文件),但是当-r
不使用该选项时继续行的处理可能导致输入不以结尾<newline>。如果输入文件的最后一行以<backslash> <newline>结尾,则会发生这种情况。出于这个原因,在描述中的“终止<newline>(如果有)应从输入中删除”中使用“如果有”。对于标准输入为文本文件的要求并不是放松。
该理由必须意味着该文本文件应以换行符结尾。
文本文件的POSIX定义为:
3.395文本文件
包含以零行或更多行组织的字符的文件。这些行不包含NUL字符,并且长度不能超过{LINE_MAX}个字节,包括<newline>字符。尽管POSIX.1-2008不能区分文本文件和二进制文件(请参阅ISO C标准),但是许多实用程序在对文本文件进行操作时只能产生可预测或有意义的输出。具有此类限制的标准实用程序始终在其STDIN或INPUT FILES部分中指定“文本文件”。
这不是直接规定“以<newline>结尾”,而是遵循C标准。
“无终端换行”问题的解决方案
注意戈登·戴维森的答案。一个简单的测试表明他的观察是准确的:
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
因此,他的技术:
while read line || [ -n "$line" ]; do echo $line; done < y
要么:
cat y | while read line || [ -n "$line" ]; do echo $line; done
将适用于末尾没有换行符的文件(至少在我的机器上)。
我仍然惊讶地发现shell删除了输入的最后一段(它不能称为行,因为它没有以换行符结尾),但是POSIX中可能有足够的理由这样做。显然,最好确保您的文本文件确实是以换行符结尾的文本文件。
cat somefile | while read
设置的所有变量while
。您可能想要while read ...; done <somefile
代替;参见BashFAQ#24。