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

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?

提问人:Mathias Bynens 提问时间:11/12/2010 最后编辑:Mathias Bynens 更新时间:10/10/2021 访问量:93106

问:

假设我有以下 Bash 脚本:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

我四处寻找解决方案,发现:

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

因此,与其测试读取出口,不如测试读取出口 状态,测试标志,并具有 读取命令将该标志从 在循环体内。那边 无论读取退出状态如何, 整个循环体运行,因为读取 只是命令列表之一 像其他任何一样在循环中,而不是 循环是否将 跑起来吧。

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

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

bash 换行符 EOF

评论


答:

19赞 netcoder 11/12/2010 #1

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

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

评论

0赞 Mathias Bynens 11/12/2010
啊,重定向让我感到困惑。感谢您的回答,它就像一个魅力!
3赞 Dennis Williamson 11/12/2010
如果存在最后一个换行符,为了避免处理额外的(空)行,请在包含以下内容的行后添加以下内容:read[[ ! $REPLY ]] && continue
0赞 Mathias Bynens 11/13/2010
@Dennis:这似乎跳过了所有的空行,这正是我在这种情况下想要的。谢谢!
46赞 Adam Bryzak 2/16/2011 #2

我使用以下构造:

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

它几乎可以处理输入中的空字符以外的任何内容:

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

评论

7赞 Richard Hansen 6/19/2011
您不需要在 IFS 中包含换行符;您可以简单地将其设置为空字符串。另外,要使此 POSIX 兼容(目前它仅适用于 bash,不适用于 ),请执行/bin/shIFS='' read -r LINE || [ -n "$LINE" ]
4赞 l0b0 9/11/2012
请注意,如果还没有换行符,这会在 EOF 处添加一个额外的换行符。
1赞 vaab 1/3/2015 #3

这里的基本问题是,当它遇到 EOF 时,即使它仍然会正确地馈送变量,也会返回 errorlevel 1。read

因此,您可以立即在循环中使用 errorlevel of,otherwize,不会解析最后的数据。但是你可以这样做: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 字符将被忽略
  • 如果您坚持使用来模仿的行为,则需要强制检测到 EOF (您可以使用条件echocatecho -n[ "$eof" == true ])
6赞 Jahid 7/14/2015 #4

使用 while 循环:grep

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

使用 instead of 将跳过空行。grep .grep ""

注意:

  1. 使用可保持任何行缩进完好无损。IFS=

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

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

评论

1赞 thanos.a 4/2/2020
在第 3 个注释上添加:这就是 POSIX 标准定义一行的方式(第 3206 行):pubs.opengroup.org/onlinepubs/9699919799/basedefs/......
0赞 unsynchronized 6/7/2016 #5

@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赞 prabhakaran9397 11/3/2016 #6

与其读取,不如尝试使用 GNU Coreutils,如 teecat 等。

与Stdin相比

readvalue=$(tee)
echo $readvalue

从文件

readvalue=$(cat filename)
echo $readvalue
2赞 Olli K 3/7/2017 #7

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

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

这是有效的,因为即使循环以非零代码从出口的“测试”结束,仍然填充内置变量(或您选择分配的任何变量)。whilereadread$REPLYread

1赞 Nathan S.R. 9/27/2021 #8

对于读取末尾带或不带换行符的文件:

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

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