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) &
或者以相同的方式,如果需要明确的生命周期控制,则对第一个命令使用 coproc
ess。
另一种解决方案是在管道中的 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 或环境变量,以在需要时传输退出状态。
写{...} | {...} &
时,至少涉及三个个进程:
- 在
|
的 LHS 中执行命令的那个
- 在
|
的RHS中执行命令的那个
- 执行整个管道的那个
&
是一个 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
我正在启动 2 个进程,其中一个通过管道连接到另一个,然后将它们都置于后台。 我想等到第二个过程结束后再决定第一个过程的信仰。
以下代码片段给出了我期望的输出(“exit 1”)。但是,wait
似乎要等待两个进程完成(所以 5 秒),这不是我想要的。
我对wait
有什么误解?
我怎样才能让它只等待管道中的第二个进程并决定稍后等待第一个(或杀死它)?
#!/bin/sh
{
sleep 5
exit 5
} | {
sleep 1
exit 1
} &
wait $!
echo "exit $?"
您可以创建 fifo
并自己控制每个进程,而不是使用管道。
最简单的方法就是使用进程替换。
{
cmd2
} < <(cmd1) &
或者以相同的方式,如果需要明确的生命周期控制,则对第一个命令使用 coproc
ess。
另一种解决方案是在管道中的 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 或环境变量,以在需要时传输退出状态。
写{...} | {...} &
时,至少涉及三个个进程:
- 在
|
的 LHS 中执行命令的那个
- 在
|
的RHS中执行命令的那个
- 执行整个管道的那个
&
是一个 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