在Shell脚本中后台进程接收到EOF

3

这种行为很难解释,所以这里提供一个可以重现的示例(在 macOS 上测试过)。

首先,我有以下C文件。细节并不重要,但基本上我使用read系统调用从标准输入读取16个字节,或者在遇到EOF时停止读取。注意,read在遇到EOF时会返回0。

// test.c

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    char buf[1];
    for (int i = 0; i < 16; i++) {
        if (read(STDIN_FILENO, buf, 1) == 0) {
            printf("EOF encountered! Number of bytes read: %d\n", i);
            return 0;
        }
    }
    printf("Read all 16 bytes\n");
    return 0;
}

假设我将这个文件编译成名为test的二进制文件。以下是程序的运行过程:

$ echo 'sixteen bytes!!' | ./test
Read all 16 bytes

$ echo '' | ./test
EOF encountered! Number of bytes read: 1

$ ./test # Waits for user input

有道理吧?我甚至可以将最后一个命令作为后台进程运行(虽然这很无用):

$ ./test &
[1] 19204

[1]  + suspended (tty input)  ./test

假设我将以下命令放入以下shell脚本中(名为huh.sh):

#!/bin/sh

./test &

当我运行这个shell脚本时,下面是输出结果:
$ ./huh.sh

EOF encountered! Number of bytes read: 0

这意味着read在遇到EOF时立即退出,而这种情况只会发生在shell脚本的上下文中。

如果我将test替换为另一个对EOF敏感的程序,我会看到类似的行为。例如,如果我直接在终端中运行node &,我将能够在ps的输出中看到node进程。但是,如果我在shell脚本中运行node &,它会立即退出。

有人能解释一下吗?

2个回答

3
在Bash中,与在终端输入的后台命令不同,shell脚本中的后台命令是以不同方式运行的。请参阅Bash手册中关于命令列表的说明,其中写道:
如果一个命令以控制运算符“&”结尾,shell会在子shell中异步执行该命令。这被称为在后台执行该命令,这些命令被称为异步命令。Shell不会等待命令执行完成,并且返回状态为0(真)。当作业控制未激活时(请参见作业控制),异步命令的标准输入,在没有任何明确重定向的情况下,将被重定向为/dev/null
在脚本中通常不会激活作业控制。
请注意,Bash中的这种行为符合POSIX shell的要求,正如Joseph Sible所指出的那样。

3

交互式 shell 中默认启用作业控制,但 shell 脚本默认禁用。以下是 POSIX 的相关引用from POSIX

如果禁用作业控制(请参阅 set、-m),异步列表的标准输入,在执行任何显式重定向之前,应被视为分配给具有与 /dev/null 相同属性的文件。如果启用作业控制,则不会发生这种情况。


1
非常好 - POSIX(你的答案)与Bash(我的答案)非常相似,但它们基本上定义了相同的行为。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接