【问题标题】:Reading input files by line using read command in shell scripting skips last line在 shell 脚本中使用 read 命令逐行读取输入文件会跳过最后一行
【发布时间】:2013-06-24 04:38:37
【问题描述】:

我通常使用 read 命令将输入​​文件逐行读取到 shell 脚本中。如果没有在输入文件 blah.txt 的最后一行的末尾插入新行,则下面的示例代码会产生错误的结果。

#!/bin/sh

while read line
do
echo $line
done <blah.txt

所以如果输入文件读到类似 -

One 
Two
Three
Four

并且我在四点之后没有按回车,脚本无法读取最后一行,并打印

One
Two
Three

现在如果我在四之后多留一个空行,比如,

One 
Two
Three
Four
//blank line

输出打印所有行,包括四行。但是,当我使用cat 命令读取一行时,情况并非如此;包括最后一行在内的所有行都会打印出来,而我不必在最后添加一个额外的空白行。

有人知道为什么会这样吗?我创建的脚本主要由其他人运行,因此他们不必在每个输入文件的末尾添加一个额外的空白行。

多年来,我一直在努力解决这个问题;如果您有任何解决方案,我将不胜感激(当然,cat 命令是其中之一,但我想知道 read 无法正常工作的原因)。

【问题讨论】:

    标签: bash parsing shell file-io


    【解决方案1】:

    read 一直读取直到找到换行符或文件结尾,如果遇到文件结尾则返回非零退出代码。所以它很有可能同时读取一行并返回一个非零的退出代码。

    因此,如果输入可能不被换行符终止,则以下代码是不安全的:

    while read LINE; do
      # do something with LINE
    done
    

    因为while 的主体不会在最后一行执行。

    从技术上讲,不以换行符结尾的文件不是文本文件,文本工具可能会以奇怪的方式在此类文件上失败。但是,我总是不愿依赖这种解释。

    解决问题的一种方法是测试读取的内容是否为非空 (-n):

    while read -r LINE || [[ -n $LINE ]]; do
      # do something with LINE
    done
    

    其他解决方案包括使用mapfile 将文件读入数组,通过某些实用程序传送文件,该实用程序保证正确终止最后一行(例如grep .,如果您不想处理空白行),或使用awk 之类的工具进行迭代处理(这通常是我的偏好)。

    请注意,-r 几乎肯定在 read 内置函数中是必需的;它会导致read 不会重新解释输入中的\-sequences。

    【讨论】:

    • 我喜欢grep . 解决方案。很好很简单。
    • @rici ||[[ -n $LINE ]] 在这里是什么意思? || 是指或吗?
    • @Unknown:如果cmd1 成功或cmd2 成功,则cmd1 || cmd2 成功的意思是“或”。 (成功意味着退出代码为 0。)read 如果遇到换行符则成功;如果测试表达式(在这种情况下 $LINE 不为空)为真,[[ 成功。
    • 干得好,您可能想提一下-n 测试实际测试的内容——它测试字符串的长度是否非零。与[[ ! -z "${LINE}" ]]相同。
    【解决方案2】:
    DONE=false
    until $DONE
    do
      read line || DONE=true
      echo $line
    done < blah.txt
    

    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?

    【讨论】:

      【解决方案3】:

      像这样使用while循环:

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

      或者使用 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. Don't read lines with for

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

      【讨论】:

        【解决方案4】:

        一行答案:

        IFS=$'\n'; for line in $(cat file.txt); do echo "$line" ; done
        

        【讨论】:

        • 如果行中有空格将不起作用 - 将视为单独的行
        • 感谢 schmi 的评论,您是对的,我编辑了答案并将 IFS=$'\n';首先这告诉 cat 命令只使用换行符作为分隔符。答案仍然是一行:)
        • 这会改变当前 shell 的IFSAnd you shouldn't read lines with for
        • 您可以稍后使用未设置的 IFS 更改 IFS,关于性能良好的效率,它只需要更多的内存,例如文件大小的两倍,因为它完全保存在内存中,但随着今天的进步,内存很便宜而且不是问题.我同意你的观点,大文件最好使用一段时间,我的回答只是展示一种很多人想要的解决问题的方法:简单
        【解决方案5】:

        下面带有重定向“while-read”循环的代码对我来说很好

        while read LINE
        do
              let count++
              echo "$count $LINE"
        
        done < $FILENAME
        
        echo -e "\nTotal $count Lines read"
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-06-06
          • 2013-03-07
          • 2012-10-09
          • 1970-01-01
          • 1970-01-01
          • 2013-12-26
          • 1970-01-01
          相关资源
          最近更新 更多