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