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

时间:2010-11-12 13:34:29

标签: bash newline eof

假设我有以下Bash脚本:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

我一直在寻找解决方案and found this

  

当读取到达文件结尾时   在行尾,它确实在读取   数据并将其分配给变量,   但它以非零状态退出。   如果你的循环是“同时构建的”   阅读;做事;完成      

所以不要测试读取退出   状态直接,测试一个标志,并有   read命令设置该标志来自   在循环体内。那样   无论读取退出状态如何   整个循环体运行,因为阅读   只是命令列表中的一个   在循环中像任何其他,而不是   决定循环的因素   完全运行。

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

如何重写此解决方案,使其行为与我之前使用的while循环完全相同,即不对输入文件的位置进行硬编码?

7 个答案:

答案 0 :(得分:34)

我使用以下构造:

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

除了输入中的空字符外,它几乎可以使用任何东西:

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

答案 1 :(得分:13)

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

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

答案 2 :(得分:5)

grep与while循环一起使用:

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

使用grep .代替grep ""将跳过空行。

注意:

  1. 使用IFS=可以保持任何行缩进。

  2. You should almost always use the -r option with read.

  3. 末尾没有换行符的文件不是标准的unix文本文件。

答案 3 :(得分:2)

而不是读取,尝试使用 GNU Coreutils ,如 tee cat 等。

来自stdin

readvalue=$(tee)
echo $readvalue

来自档案

readvalue=$(cat filename)
echo $readvalue

答案 4 :(得分:1)

这里的基本问题是read在遇到EOF时会返回错误级别1,即使它仍然正确地提供变量。

因此,你可以立即在你的循环中使用read的错误级别,其他文件,最后的数据不会被解析。但你可以这样做:

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

如果你想要一种非常可靠的方法来解析你的行,你应该使用:

IFS='' read -r LINE

请记住:

  • NUL字符将被忽略
  • 如果您坚持使用echo来模仿cat的行为,则需要在检测到EOF时强制echo -n(您可以使用条件{{1} })

答案 5 :(得分:0)

@Netcoder的答案很好,这种优化消除了虚假的空白行,也允许最后一行没有换行符,如果原来是这样的话。

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

我使用了它的一个变体来创建2个函数,以允许包含'['的文本管道以保持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启用管道,否则只是翻译参数)

答案 6 :(得分:0)

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

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

哪个有效,因为即便如此while循环结束为&#34; test&#34;从read退出非零代码,read仍会填充内置变量$REPLY(或您选择使用read分配的任何变量)。