【问题标题】:How to use `while read` (Bash) to read the last line in a file if there’s no newline at the end of the file?如果文件末尾没有换行符,如何使用 `while read` (Bash) 读取文件的最后一行?
【发布时间】:2011-05-09 02:03:16
【问题描述】:

假设我有以下 Bash 脚本:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

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

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

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

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

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

【问题讨论】:

    标签: bash newline eof


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

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

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

      这很有效,因为即使 while 循环以非零代码退出 read 的“测试”,read 仍会填充内置变量 $REPLY(或您选择的任何变量分配read)。

      【讨论】:

        【解决方案3】:

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

        来自标准输入

        readvalue=$(tee)
        echo $readvalue
        

        来自文件

        readvalue=$(cat filename)
        echo $readvalue
        

        【讨论】:

          【解决方案4】:

          @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 启用管道,否则只翻译参数)

          【讨论】:

            【解决方案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 文本文件。

            【讨论】:

            【解决方案6】:

            我使用以下构造:

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

            除了输入中的空字符之外,它几乎适用于任何东西:

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

            【讨论】:

            • 您不需要在 IFS 中包含换行符;您可以简单地将其设置为空字符串。另外,为了使这个 POSIX 兼容(目前它只适用于 bash,而不是 /bin/sh),请执行 IFS='' read -r LINE || [ -n "$LINE" ]
            • 请注意,如果没有,这会在 EOF 处添加一个额外的换行符。
            【解决方案7】:

            这里的基本问题是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 ]

            【讨论】:

              【解决方案8】:

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

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

              【讨论】:

              • 啊,重定向让我很困惑。感谢您的回答,它就像一个魅力!
              • 如果最后一个换行符,为了避免处理额外的(空)行,在包含read的行之后添加这个:[[ ! $REPLY ]] &amp;&amp; continue
              • @Dennis:这似乎跳过了所有空行,这正是我在这种情况下想要的。谢谢!
              猜你喜欢
              • 1970-01-01
              • 2014-05-28
              • 2017-05-24
              • 1970-01-01
              • 2019-12-16
              • 1970-01-01
              • 1970-01-01
              • 2011-03-16
              • 1970-01-01
              相关资源
              最近更新 更多