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

发布于 2024-10-02 20:26:22 字数 680 浏览 3 评论 0原文

假设我有以下 Bash 脚本:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

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

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

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

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

<前><代码>完成=假 直到 $DONE ;do 阅读||完成=真 # 在这里处理 $REPLY 完成< /路径/到/文件.in

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

Let’s say I have the following Bash script:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

I noticed that for files without a newline at the end, this will effectively skip the last line.

I’ve searched around for a solution and found this:

When read reaches end-of-file instead
of end-of-line, it does read in the
data and assign it to the variables,
but it exits with a non-zero status.
If your loop is constructed "while
read ;do stuff ;done

So instead of testing the read exit
status directly, test a flag, and have
the read command set that flag from
within the loop body. That way
regardless of reads exit status, the
entire loop body runs, because read
was just one of the list of commands
in the loop like any other, not a
deciding factor of if the loop will
get run at all.

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

How can I rewrite this solution to make it behave exactly the same as the while loop I was having earlier, i.e. without hardcoding the location of the input file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

痴意少年 2024-10-09 20:26:22

我使用以下构造:

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

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

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

I use the following construct:

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

It works with pretty much anything except null characters in the input:

  • Files that start or end with blank lines
  • Lines that start or end with whitespace
  • Files that don't have a terminating newline
千紇 2024-10-09 20:26:22

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

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

In your first example, I'm assuming you are reading from stdin. To do the same with the second code block, you just have to remove the redirection and echo $REPLY:

DONE=false
until $DONE; do
    read -r || DONE=true
    printf '%s\n' "$REPLY"
done
不喜欢何必死缠烂打 2024-10-09 20:26:22

grep 与 while 循环一起使用:

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

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

注意:

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

  2. 您几乎应该始终使用 -r 选项进行读取。

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

Using grep with while loop:

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

Using grep . instead of grep "" will skip the empty lines.

Note:

  1. Using IFS= keeps any line indentation intact.

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

  3. File without a newline at the end isn't a standard unix text file.

毅然前行 2024-10-09 20:26:22

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

stdin 中

readvalue=$(tee)
echo $readvalue

尝试从文件的

readvalue=$(cat filename)
echo $readvalue

Instead of read, try to use GNU Coreutils like tee, cat, etc.

from stdin

readvalue=$(tee)
echo $readvalue

from file

readvalue=$(cat filename)
echo $readvalue
滥情哥ㄟ 2024-10-09 20:26:22

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

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

它之所以有效,是因为即使 while 循环随着 read 中的“测试”以非零代码退出而结束, read 仍然填充内置变量 $REPLY (或您选择使用 read 分配的任何变量)。

This is the pattern I've been using:

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

Which works because even tho' the while loop ends as the "test" from the read exits with a non-zero code, read still populates the inbuilt variable $REPLY (or whatever variables you choose to assign with read).

牵强ㄟ 2024-10-09 20:26:22

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

所以你可以在循环中立即使用read的errorlevel,否则,最后的数据将不会被解析。但你可以这样做:

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 ]

The basic issue here is that read will return errorlevel 1 when it encounters EOF, even if it'll still correctly feed the variable.

So you can use errorlevel of read right away in your loop, otherwize, the last data won't be parsed. But you could do this:

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

If you want a very solid way to parse your lines, you should use:

IFS='' read -r LINE

Remember that:

  • NUL character will be ignored
  • if you stick to using echo to mimick the behavior of cat you'll need to force an echo -n upon EOF detected (you can use the condition [ "$eof" == true ])
神仙妹妹 2024-10-09 20:26:22

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

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

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

for reading files with or without a newline at the end:

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

Source : My open source project https://sourceforge.net/projects/command-output-to-html-table/

毁我热情 2024-10-09 20:26:22

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

@Netcoder's answer is good, this optimisation eliminates spurious blank lines, also allows for the last line not to have a newline, if that's how the original was.

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

I used a variant of this to create 2 functions to allow piping of text that includes a '[' to keep grep happy. (you can add other translations)

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
 }

(passing - as $1 enables pipe otherwise just translates arguments)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文