shell 脚本中的后台进程收到 EOF

Background process in shell script receives EOF

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

首先,我有以下 C 文件。细节并不重要,但我基本上使用 read system call 从标准输入读取 16 个字节,或者直到遇到 EOF。请注意,read 将在 EOF 上 return 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 脚本的上下文中发生

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

谁能解释一下?

在 Bash 中,shell 脚本中的后台命令与在终端键入的后台命令 运行 不同。请参阅 Lists of Commands 上的 Bash 手册,其中写道:

If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background, and these are referred to as asynchronous commands. The shell does not wait for the command to finish, and the return status is 0 (true). When job control is not active (see Job Control), the standard input for asynchronous commands, in the absence of any explicit redirections, is redirected from /dev/null.

作业控制通常不会在脚本中激活。

请注意,Bash 中的此行为与 , as noted by Joseph Sible 的要求一致。

作业控制在交互式 shell 中默认启用,但在 shell 脚本中默认禁用。这是一个相关的引用 from POSIX:

If job control is disabled (see set, -m), the standard input for an asynchronous list, before any explicit redirections are performed, shall be considered to be assigned to a file that has the same properties as /dev/null. This shall not happen if job control is enabled.