给定两个后台命令,当其中一个退出时终止剩余的命令

给定两个后台命令,当其中一个退出时终止剩余的命令

我有一个简单的 bash 脚本来启动两个服务器:

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

如果第二个命令退出,则第一个命令似乎继续运行。

如何更改此设置,以便如果任一命令退出,另一个命令就会终止?

请注意,我们不需要检查后台进程的错误级别,只需检查它们是否已退出。

答案1

这会启动两个进程,等待第一个进程完成,然后杀死另一个进程:

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

怎么运行的

  1. 开始:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &
    

    上述两个命令在后台启动两个进程。

  2. 等待

    wait -n
    

    这会等待任一后台作业终止。

    由于该-n选项的原因,这需要 bash 4.3 或更高版本。

  3. pkill -P $$
    

    这会终止当前进程作为父进程的所有作业。换句话说,这会杀死所有仍在运行的后台进程。

    如果您的系统没有pkill,请尝试将此行替换为:

    kill 0
    

    这也杀死当前进程组

易于测试的示例

通过更改脚本,即使没有gulp安装,我们也可以测试它:

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

上面的脚本可以运行,bash script.sh 1 3并且第一个进程首先终止。或者,可以运行它,bash script.sh 3 1第二个进程将首先终止。无论哪种情况,人们都可以看到这按预期运行。

答案2

在我的系统(Centos)上,wait没有-n,所以我这样做了:

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

这不会等待“任一”,而是等待第一个。但如果您知道哪个服务器将首先停止,它仍然会有所帮助。

答案3

这很棘手。这是我的设计;也许可以简化/精简它:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • 首先,启动一个while true; do sleep 42; done &永远休眠/暂停的进程 ( )。如果您确定两个命令将在一定时间内(例如,一个小时)内终止,您可以将其更改为超过该时间的单个睡眠(例如,sleep 3600)。然后,您可以更改以下逻辑以将其用作超时;即,如果进程在这么长时间后仍在运行,则终止它们。 (请注意,上述脚本目前并未执行此操作。)
  • 启动两个异步(并发后台)进程。
    • 你不需要./cd
    • command & echo "$!" > somewhere; wait "$!" 是一个棘手的结构,它异步启动一个进程,捕获它的 PID,然后等待它;使其成为前台(同步)进程。但这发生在(…)整个后台的列表中,因此gulp进程确实异步运行。
    • 任一gulp进程退出后,将其状态写入临时文件并终止“永远睡眠”进程。
  • sleep 1以防止出现竞争情况,即第一个后台进程在第二个后台进程有机会将其 PID 写入文件之前终止。
  • 等待“永远睡眠”进程终止。这发生在之后任何一个gulp如上所述,进程退出。
  • 查看哪个后台进程终止了。如果失败了,就杀死另一个。
  • 如果一个进程失败并且我们杀死了另一个进程,请等待第二个进程结束并将其状态保存到文件中。如果第一个进程成功完成,请等待第二个进程完成。
  • 检查两个进程的状态。

答案4

为了完整起见,这是我最终使用的:

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

这对我来说适用于 Windows 2.5.3 64 位的 Git。旧版本可能不接受-n上的选项wait

相关内容