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

问题描述 投票:0回答:8

假设我有以下 Bash 脚本:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

我四处寻找解决方案并找到了这个

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

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

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

如何重写此解决方案,使其行为与我之前的

while
循环完全相同,即无需对输入文件的位置进行硬编码?

bash newline eof
8个回答
48
投票

我使用以下结构:

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

它几乎适用于输入中除空字符之外的任何内容:

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

19
投票

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

DONE=false
until $DONE; do
    read -r || DONE=true
    printf '%s\n' "$REPLY"
done

6
投票

grep
与 while 循环一起使用:

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

使用

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

注:

  1. 使用

    IFS=
    可以保持任何行缩进完好无损。

  2. 您几乎应该始终将 -r 选项与 read 一起使用。

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


5
投票

尝试使用 GNU Coreutils,而不是 read,例如 teecat

来自标准输入

readvalue=$(tee)
echo $readvalue

来自文件

readvalue=$(cat filename)
echo $readvalue

2
投票

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

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

这是有效的,因为即使

while
循环结束,因为
read
中的“测试”以非零代码退出,
read
仍然填充内置变量
$REPLY
(或您选择分配的任何变量)与
read
)。


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
    (您可以使用条件
    [ "$eof" == true ]

1
投票

用于读取末尾有或没有换行符的文件:

cat "somefile" | { cat ; echo ; } | while read line; do echo $line; done

来源:我的开源项目https://sourceforge.net/projects/command-output-to-html-table/


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 启用管道,否则仅转换参数)

© www.soinside.com 2019 - 2024. All rights reserved.