如果在文件末尾没有换行符,如何使用'read`(Bash)读取文件的最后一行?

比方说,我有以下的Bash脚本:

while read SCRIPT_SOURCE_LINE; do echo "$SCRIPT_SOURCE_LINE" done 

我注意到,对于最后没有换行符的文件,这将有效地跳过最后一行。

我search了一个解决scheme, 并发现这一点 :

当读取到达文件结束而不是行结束时,读取数据并将其分配给variables,但是它以非零状态退出。 如果你的循环是“读,做,做,做”

因此,不是直接testing读出口状态,而是testing一个标志,并使读命令在循环体内设置该标志。 这样,无论读取退出状态如何,整个循环体都会运行,因为读取只是循环中的命令列表之一,而不是决定循环是否可以运行的决定性因素。

 DONE=false until $DONE ;do read || DONE=true # process $REPLY here done < /path/to/file.in 

我怎样才能重写这个解决scheme,使其行为与我之前的while循环完全相同,即不对input文件的位置进行硬编码?

在你的第一个例子中,我假设你正在读stdin 。 要对第二个代码块执行相同的操作,只需删除redirect并回显$ REPLY:

 DONE=false until $DONE ;do read || DONE=true echo $REPLY done 

我使用以下构造:

 while IFS= read -r LINE || [[ -n "$LINE" ]]; do echo "$LINE" done 

它几乎适用于input中的空字符除外:

  • 以空行开头或结尾的文件
  • 以空白开始或结束的行
  • 没有终止换行符的文件

在while循环中使用grep

 while IFS= read -r line; do echo "$line" done < <(grep "" file) 

使用grep . 而不是grep ""将跳过空行。

注意:

  1. 使用IFS=保持任何行缩进不变。

  2. 你应该几乎总是使用阅读-r选项。

  3. 最后没有换行的文件不是标准的unix文本文件。

不要阅读 ,请尝试使用GNU Coreutils,例如T恤等。

从标准input

 readvalue=$(tee) echo $readvalue 

从文件

 readvalue=$(cat filename) echo $readvalue 

这里的基本问题是,当遇到EOF时, read将返回错误级别1,即使它仍然会正确地馈送variables。

所以你可以在你的循环中使用readle错误级别,其他的数据不会被parsing。 但是你可以这样做:

 eof= while [ -z "$eof" ]; do read SCRIPT_SOURCE_LINE || eof=true ## detect eof, but have a last round echo "$SCRIPT_SOURCE_LINE" done 

如果你想要一个非常可靠的方法来parsing你的线,你应该使用:

 IFS='' read -r LINE 

请记住:

  • NUL字符将被忽略
  • 如果你坚持用echo来模仿cat的行为,你需要在EOF检测到的时候强制echo -n (你可以使用条件[ "$eof" == true ]

@ Netcoder的答案是好的,这种优化消除虚假的空白行,也允许最后一行不换行,如果这是原来的。

 DONE=false NL= until $DONE ;do if ! read ; then DONE=true ; NL='-n ';fi echo $NL$REPLY done 

我使用了这个变体来创build2个函数来允许包含'['的文本的pipe道保持grep快乐。 (你可以添加其他翻译)

 function grepfix(){ local x="$@"; if [[ "$x" == '-' ]]; then local DONE=false local xx= until $DONE ;do if ! IFS= read ; then DONE=true ; xx="-n "; fi echo ${xx}${REPLY//\[/\\\[} done else echo "${x//\[/\\\[}" fi } function grepunfix(){ local x="$@"; if [[ "$x" == '-' ]]; then local DONE=false local xx= until $DONE ;do if ! IFS= read ; then DONE=true ; xx="-n "; fi echo ${xx}${REPLY//\\\[/\[} done else echo "${x//\\\[/\[}" fi } 

(通过 – 如$ 1启用pipe道,否则只是翻译参数)

这是我一直在使用的模式:

 while read -r; do echo "${REPLY}" done [[ ${REPLY} ]] && echo "${REPLY}" 

这是因为即使while循环结束,当read的“testing”以非零代码退出时, read仍填充内置variables$REPLY (或者您select分配的任何variables)。