如何检测 BASH 脚本的 stdin 上的 EOF?

如何检测 BASH 脚本的 stdin 上的 EOF?

我的脚本中有一个 bash 函数,它需要从固定大小的块中的标准输入读取数据,并将这些数据一次一个发送到外部程序以进行进一步处理。只要有数据,函数本身就应该在循环中运行(输入始终保证是整数块),但它不需要解释数据,所以我想要一个在函数的 stdin 上检测 EOF 的方法,而不消耗数据,以防仍有一些数据需要处理。

显然,执行此操作的自然方法是使用read内置函数,如下所示:

while read -r -n 0 ; do external_program ; done

选项-n告诉read它最多只读取这么多字节,而不是最多读取换行符,但不幸的是它不适用于 0 字节,这将使其成为 EOF 的理想测试。它确实可以与 一起使用-n 1,但随后它会消耗块的第一个字节,该块必须“重播”到进入外部程序的流中。

那么,有没有更好的方法,最好只使用 bash 内置函数?

答案1

我不确定您是否可以在不实际尝试读取一些非零字节数的情况下检测到 EOF。

read()那是因为系统调用没有返回值明确地表示文件结束。相反,您得到的只是“读取零字节,没有错误”,并且由应用程序代码知道这意味着什么。在常规文件上,当您读取文件末尾或超出文件末尾且没有剩余数据时,显然会发生这种情况。

但在终端上,可能会发生这种情况,因为用户在空行上点击 ^D,导致终端界面返回此时的内容,即什么也没有;在数据报套接字上,可以发送和接收零长度消息。这些情况都没有表示实际结束:终端可以在 ^D 之后读取数据,并且套接字可能会在零长度消息之后接收其他消息。 (即使在常规文件上,如果同时有其他进程附加到该文件,后续文件也可能会返回数据。重复读取 EOF 是 的简单实现tail -f。)

如果您明确要求读取零字节,您也会得到零字节(或错误),无论您是否处于 EOF。


如果外部程序能够处理 EOF 而没有太多麻烦,最好只返回一个退出代码来表明这一点,那么可能会得到最好的结果。然后你会这样做:

while external_program; do
    # do we need to do anything here but loop?
    true 
done

或者,如果我们足够幸运,我们可以获得不同的 EOF 退出状态:

while true; do
    external_program
    ret=$?
    if [ "$ret" = 0 ]; then
        echo "ok, continue"
    elif [ "$ret" = 1 ]; then
        echo "deal with this error"
        # but what now?
    elif [ "$ret" = 2 ]; then
        echo "got EOF, stopping"
        break
    fi
done

让该程序处理 EOF 是有意义的,因为它需要验证它收到的输入。

如果你做不到这一点,你可以让 Bash 读取数据块并将其传递给程序(如果实际读取了足够的数据):

blocksize=123
while IFS= read -d '' -r -n "$blocksize" data && [ "${#data}" = "$blocksize"]; do
    printf "%s" "$data" | externalprogram
done

但这仅在数据从不包含 NUL 字节 ( \0) 的情况下才适用于 Bash。如果是这样,您需要切换到 Zsh(或某种真正的编程语言),或使用类似的语言head -c "$blocksize" > tmpfile

相关内容