如何在 bash 脚本中等待所有生成的进程和后台进程完成

如何在 bash 脚本中等待所有生成的进程和后台进程完成

我看了又看,找不到一个 bash 脚本的工作解决方案,我正在尝试创建该脚本来关闭进程并等待它并生成进程完成。我还在学习很多 Linux 知识。

语境:进程 FOO 运行。进程BAR用于检查FOO,也用于杀死它(我无法控制这两个进程的内部)。我所能做的就是将命令传递给 BAR,然后它执行它们。

在本例中,我向 BAR 发送命令来终止 FOO,它会生成另一个进程并将其置于后台。

目标:我试图同时运行 20 个命令来对 20 个 FOO 执行终止(通过 BAR),并等待所有 FOO 死亡,然后再继续执行脚本的下一部分。

问题:到目前为止,我所能做的就是等待 BAR 执行,并在后台进程杀死 FOO 之前继续执行。

BAR exit FOO1
BAR exit FOO2
...
BAR exit FOO20
wait
do more stuff

我也尝试过

BAR exit FOO1
PID1=$!
BAR exit FOO2
PID2=$!
wait $PID1 $PID2

没有运气。

答案1

如果您没有正在等待的其他进程,wait则不带参数的 a 将等待所有后台作业完成。我经常使用类似的结构

for i in *; do md5sum $i & done
wait

但是,如果您的脚本有任何其他后台任务,这将会中断,但您可以将其包装在子 shell 中,例如

(for i in *; do md5sum $i & done; wait) & all_md5sum=$!
...
wait $all_md5sum

答案2

首先尝试一个更简单的场景,例如只有 2 个进程FOO1FOO2,并且如果您要在名为 的脚本中运行parent.sh

#!/bin/bash

FOO1 &
pid1=$!

echo "Child PID=$pid1 launched"

FOO2 &
pid2=$!

echo "Child PID=$pid2 launched"

BAR exit FOO1
BAR exit FOO2

echo "Pausing to wait for children to finish..."

wait $pid1
wait $pid2

echo "Children finished, moving on."

看看这是否适用于 2 个FOOs,如果适用,则适用于 20 个。

解释

由于我们无法看到FOOs 和BAR流程的内部结构,因此我仅依赖您发布的内容,据我了解,您说

  • “进程BAR用于检查FOO,也用于杀死它”
  • 意思是,在您发布片段之前的某个时刻,您以FOO1某种方式开始,以便BAR exit FOO1影响它

在您最初发布的片段中,您还写道:

BAR exit FOO1
PID1=$!
  • $!捕获最近的后台进程
  • BAR exit FOO1不是一个进入后台的进程,它正如你所说,是使用 BAR 控制 FOO 的一种手段:“BAR 用于检查 FOO,也用于杀死它”
  • 因此$!不太可能捕获FOO1pid

因此,FOO请确保在实际启动 * 进程的地方捕获 pid。例如,如果您全部运行在 中parent.sh,并且只有 2 个进程,您将执行以下操作:

#!/bin/sh

FOO1 &
pid1=$!

echo "Child PID=$pid1 launched"

FOO2 &
pid2=$!

echo "Child PID=$pid2 launched"
  • 小写pid1,你也可以使用原来的大写,只要一致,我只是更喜欢使用小写作为约定,以区别被误认为是环境变量
  • #echo 是可选跟踪,因此我们知道发生了什么,在排除故障时使用它,并在解决此问题后将其注释掉或删除它

然后我们执行您声称用于检查和终止的命令FOO1FOO2

BAR exit FOO1
BAR exit FOO2

然后,

echo "Pausing to wait for children to finish..."

wait $pid1
wait $pid2

echo "Children finished, moving on."
  • 同样,echos 是可选的,但在您仍在排除故障时提供信息,让我们知道发生了什么
  • wait对于每个人$pid...
  • 接下来是另一个选项echo,让您知道waiting 已结束

信用

射击 2009免费 pdf 第 506 页上的异步脚本示例

答案3

您可以为每个命令触摸一个文件并检查该文件。这是一个简单的例子:

#!/usr/local/bin/bash

# Flag to knkow when completed
finished=0;

# The commands t run
declare -a commands=('cmd1' 'cmd2' 'cmd3' 'cmd4');

# Fork all commands
for cmd in "${commands[@]}"; do
        ./$cmd &
done

# Loop to wait for all processes to finish
loop=0;
while [ $finished -eq 0 ]; do
        # Just for visual effect
        loop=$((loop+1));
        echo "Loop $loop";
        sleep 1;

        files=0;
        for cmd in "${commands[@]}"; do
                # Each program shoudl touch a file so that we
                # can know it has completed successfully
                if [ -e ".$cmd" ]; then
                        echo "Process $cmd completed";
                        files=$((files+1));
                else
                        # No need to continue if anything is not done
                        # Error checking here like a TTL value or ps -ef | grep pid etc
                        echo "Waiting for process $cmd";
                        break;
                fi;
        done

        # if we have as many files as executed commands
        if [ "$files" -eq ${#commands[@]} ]; then
                finished=1;
        fi
done;

echo 'All jobs completed successfully';
# Clean up after yourself
for cmd in "${commands[@]}"; do
        rm -f ".$cmd";
done;

然后,为了测试每个 cmd1、cmd2 ... cmdN 可以具有如下内容:

#!/usr/local/bin/bash
num=1;

echo "Enter cmd$num";
sleep 1; # use different sleep values to simulate execution
touch ".cmd$num"
echo "Exit cmd$num";

答案4

如果 $PID1 正在运行,这将评估为 true: if [kill -s 0 $PID1 ]

您可以有多个 if 子句。如果 PID 未运行,它们将评估为 false。该子句(与其他子句组合)应根据您的需要控制脚本的流程。

相关内容