bash: 等待管道中的单个进程

bash: wait for single process from a pipeline

我正在启动 2 个进程,其中一个通过管道连接到另一个,然后将它们都置于后台。 我想等到第二个过程结束后再决定第一个过程的信仰。

以下代码片段给出了我期望的输出(“exit 1”)。但是,wait 似乎要等待两个进程完成(所以 5 秒),这不是我想要的。

我对wait有什么误解? 我怎样才能让它只等待管道中的第二个进程并决定稍后等待第一个(或杀死它)?

#!/bin/sh
{
  sleep 5
  exit 5
} | {
  sleep 1
  exit 1
} &
wait $!
echo "exit $?"

您可以创建 fifo 并自己控制每个进程,而不是使用管道。

最简单的方法就是使用进程替换。

{
   cmd2
} < <(cmd1) &

或者以相同的方式,如果需要明确的生命周期控制,则对第一个命令使用 coprocess。

另一种解决方案是在管道中的 shell 和父 shell 之间进行进程间通信,无论是信号还是文件,或者 pid 都可以发送到父进程,以便父进程可以 while kill -0 仅在最后一个管道的 pid 上。

: > file
... | {
  sleep 1
  echo end > file
} &
while [[ $(cat file) != "end" ]]; do
   sleep 0.1
done

您没有误解 wait 内置行为。您误解了 shell 管道处理。

wait 命令按预期运行,它等待管道右侧部分(sleep 1)的 subshell 结束,并等待五秒钟因为这个右边的部分 在它的 管道输入关闭 之前不会有效地终止 。只有当管道的左侧部分在其终端(sleep 5)关闭它时,它的管道输入才会关闭,因此在五秒后。

要等待管道的正确部分,您可以使用信号来了解正确的部分 何时大约 像这样完成:

#!/bin/dash
{
  sleep 5
  exit 5
} | {
  sleep 1
  kill -s USR1 $$
  exit 1
} &
trap "echo \"Second shell finished\";" USR1
wait $!
echo "exit $?"

这样,您将从正确的部分获得信号,然后 wait 命令将只等待它获得这些信号(wait 命令等待任何信号状态变化,不仅仅是终止)。 kill -s USR1 $$ 将 USR1 信号发送给父级 shell。请注意,它可能意味着当前子进程的终止shell,您必须知道该进程将如何处理 USR1(我猜 sleep 会忽略它)。

但是,您无法从管道右侧的 shell 获得正确的退出代码,因为它在其管道输入关闭之前不会有效终止。更准确地说,您将在此处获得退出状态 138,这是信号状态 USR1(值 10)加上 POSIX 信号基数 (128),而不是 subshell 退出状态。您可以使用不同的信号(USR1,USR2(POSIX),非POSIX:.. USR64)来表示退出状态或使用子shell STDOUT 或环境变量,以在需要时传输退出状态。

{...} | {...} &时,至少涉及三个个进程:

  1. |
  2. 的 LHS 中执行命令的那个
  3. |
  4. 的RHS中执行命令的那个
  5. 执行整个管道的那个

& 是一个 list 终止符。它不适用于先前的简单或复合命令,甚至不适用于先前的管道。整个命令在单个后台进程中执行:

a | b || c | d && e | f &

&不仅仅适用于f,或e | f,甚至c | d && e | f。它适用于整个流水线序列。

@KamilCuk 提到命名管道。我更喜欢它们而不是进程替换,因为它允许您统一处理两个进程,至少就语法而言。 (此外,命名管道适用于任何 POSIX 兼容的 shell,而不仅仅是 bash。)

mk_fifo p1

{ sleep 5; exit 5; } > p1 & p1_pid=$!
{ sleep 1; exit 1; } < p1 & p2_pid=$!

wait $p2_pid

# Now, kill or wait for the first process as desired