读取时,如果最后一行没有以换行符(\n)结尾,请将其视为有效行。

8

我注意到有一段时间了,如果一个文件的末尾没有"换行符",read永远不会读取该文件的最后一行。如果考虑到在文件中没有"换行符"等于它包含0行(这是相当难以接受的!),这是可以理解的。例如,看下面的示例:

$ echo 'foo' > bar ; wc -l bar
1 bar

但是...

$ echo -n 'bar' > foo ; wc -l foo
0 foo

问题是:当使用read处理那些我没有创建或修改过,并且我不知道它们是否以“换行”字符结尾的文件时,我该如何处理这种情况?


1
实际上,read 命令可以很好地读取那个未终止的最后一行。问题在于在循环中使用其返回值 - 请参见我的答案 - kopischke
4个回答

15

read实际上会将一个未终止的行读入已分配变量中(默认情况下为$REPLY)。如果遇到这样的行,它还会返回false,这意味着“文件结束”;在传统的while循环中直接使用其返回值可以跳过那最后一行。如果稍微更改循环逻辑,你可以使用 read 正确处理非新行终止的文件,而无需事先对其进行清洗。

while read -r || [[ -n "$REPLY" ]]; do
    # your processing of $REPLY here
done < "/path/to/file"

请注意,这比依赖外部解决方案要快得多。

Gordon Davisson 表示感谢,因为他改进了循环逻辑。


只是好奇:这与 cat file | while read -r 有何不同? - user123444555621
1
@Pumbaa80 它不会启动外部进程,这使它更快,并且循环不在子shell中执行,再次更快,而且没有从切换shell上下文中产生意外副作用 - kopischke
2
这个方法不太可行,因为如果文件以换行符结尾,它会多运行一次循环,并将REPLY设置为空字符串。建议使用while read -r || [ -n "$REPLY" ]; do代替。 - Gordon Davisson
@GordonDavisson的评论中“this”是一个使用布尔控制变量设置为read退出值的until循环,当文件的最后一行正确终止时,确实会将$REPLY设置为空字符串。 - kopischke
@kopischke:这绝对是更干净的做法。谢谢! - michaelmeyer

2

POSIX要求文件中的任何行都必须在末尾添加换行符,以表示它是一行。但是这个网站提供了一个解决方案,可以完美地解决您所描述的情况。最终产品就是这个代码块。

newline='
'
lastline=$(tail -n 1 file; echo x); lastline=${lastline%x}
[ "${lastline#"${lastline%?}"}" != "$newline" ] && echo >> file
# Now file is sane; do our normal processing here...

这也是完全没有必要的,除非你特别希望对这些文件进行消毒处理。read可以很好地处理它们,就像我的答案所示。 - kopischke

1
如果你必须使用 read,尝试这个:
awk '{ print $0}' foo | while read line; do
    echo the line is $line
done

由于awk似乎即使没有换行符也能识别行


我更喜欢远离awk,但这仍然是一个好主意。谢谢! - michaelmeyer
这个网站解释了使用awk的风险:“事实证明,由于awk处理输入的方式,直接使用一行命令awk 1 file > tempfile && mv tempfile file可以产生正确的输出,无论原始文件是否正确。然而,如果文件很大,我们希望避免需要读取整个文件来修复最后一行(如果它是正确的,甚至不需要修复)。” - Giacomo1968
这个解决方案不需要你在awk中进行任何高级操作。实际上,如果你经常使用shell脚本,awk是一个很好的工具。学习基础知识并不需要很长时间。Awk通常用于文件处理,特别是当文件很大时,它比使用shell命令快得多。 - EJK

1
这更或多或少是迄今为止给出的答案的结合体。它不会直接修改文件。
(cat file; tail -c1 file | grep -qx . && echo) | while read line
do
    ...
done

我认为这是最好的答案,因为通过重写带有最终换行符的文件X来修改它并不总是适用于每个人。此外,这段代码易于理解(当脚本不应该是“只能写不能读”时,这总是一个好主意),而且是最短和最安全的代码(请参见JakeGould的评论)。这个一行代码也可以存储到一个单独的脚本或别名中,因此是可重用的。 - try-catch-finally
虽然我完全同意其理念,但我不同意其实现的价值。除了使用完全不必要的外部量之外,管道内循环还有自身的问题。请参见我的答案,以获取一个仅限于当前shell的内部解决方案。 - kopischke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接