shell – 获取后台进程的退出代码

我有一个从我的主Bourne shell脚本调用命令CMD永远。

我想修改脚本如下:

  1. 并行运行命令CMD作为后台进程($ CMD&)。
  2. 在主脚本中,每隔几秒钟都有一个循环来监视生成的命令。 该循环还回显了一些消息到标准输出指示脚本的进展。
  3. 生成的命令终止时退出循环。
  4. 捕获并报告产生的进程的退出代码。

有人可以给我指针来完成这个?

1:在bash中, $! 保存最后执行的后台进程的PID。 无论如何,这将告诉你要监视的过程。

4: wait <n>等待,直到具有ID的进程完成(它将阻塞,直到进程完成,所以你可能不想调用这个,直到你确定进程已经完成)。 wait返回后,进程的退出代码返回variables$?

2,3: psps | grep " $! " ps | grep " $! "可以告诉你进程是否仍在运行。 如何理解输出并决定完成的距离有多大取决于你。 ( ps | grep不是傻瓜式的,如果有时间的话可以用更强大的方法来判断进程是否还在运行)。

这里是一个框架脚本:

 # simulate a long process that will have an identifiable exit code (sleep 15 ; /bin/false) & my_pid=$! while ps | grep " $my_pid " # might also need | grep -v grep here do echo $my_pid is still in the ps output. Must still be running. sleep 3 done echo Oh, it looks like the process is done. wait $my_pid my_status=$? echo The exit status of the process was $my_status 

当我有类似的需求时,我就是这样解决的:

 # Some function that takes a long time to process longprocess() { # Sleep up to 14 seconds sleep $((RANDOM % 15)) # Randomly exit with 0 or 1 exit $((RANDOM % 2)) } pids="" # Run five concurrent processes for i in {1..5}; do ( longprocess ) & # store PID of process pids+=" $!" done # Wait for all processes to finnish, will take max 14s for p in $pids; do if wait $p; then echo "Process $p success" else echo "Process $p fail" fi done 
 #/bin/bash #pgm to monitor tail -f /var/log/messages >> /tmp/log& # background cmd pid pid=$! # loop to monitor running background cmd while : do ps ax | grep $pid | grep -v grep ret=$? if test "$ret" != "0" then echo "Monitored pid ended" break fi sleep 5 done wait $pid echo $? 

正如我所看到的几乎所有的答案使用外部工具(主要是ps )来轮询后台进程的状态。 有更多的unixesh解决scheme,捕获SIGCHLD信号。 在信号处理程序中,必须检查哪个subprocess已经停止。 可以通过kill -0 <PID>内build(通用)或检查/proc/<PID>目录(Linux专用)的存在或使用内置jobs ( bash specific。jobs jobs -l还报告在这种情况下,输出的第三个字段可以是Stopped | Running | Done | Exit。)。

这是我的例子。

启动的过程称为loop.sh 它接受-x或一个数字作为参数。 对于-x退出并退出代码1.对于一个数字,它等待num * 5秒。 每5秒钟打印一次PID。

启动过程被称为launch.sh

 #!/bin/bash handle_chld() { local tmp=() for((i=0;i<${#pids[@]};++i)); do if [ ! -d /proc/${pids[i]} ]; then wait ${pids[i]} echo "Stopped ${pids[i]}; exit code: $?" else tmp+=(${pids[i]}) fi done pids=(${tmp[@]}) } set -o monitor trap "handle_chld" CHLD # Start background processes ./loop.sh 3 & pids+=($!) ./loop.sh 2 & pids+=($!) ./loop.sh -x & pids+=($!) # Wait until all background processes are stopped while [ ${#pids[@]} -gt 0 ]; do echo "WAITING FOR: ${pids[@]}"; sleep 2; done echo STOPPED 

有关更多解释,请参阅: 从bash脚本启动进程失败

我会稍微改变你的方法。 如果命令仍处于活动状态并报告消息,则不要每隔几秒钟检查一次,还要让另一个进程每隔几秒报告一次该命令仍在运行,然后在命令完成时终止该进程。 例如:

 #!/ bin / sh的

 cmd(){sleep 5;  24号出口;  }

 cmd&#运行长时间运行的进程
 PID = $!  #loggingpid

 #产生一个可以连续报告命令仍在运行的进程
而回声“$(date):$ pid仍在运行”; 做睡1; 完成&
 echoer = $!

 #设置一个陷阱,当进程结束时杀死记者
陷阱'杀死$ echoer'0

 #等待过程完成
如果等待$ pid; 然后
    回声“cmd成功”
其他
    回声“CMD失败!!(返回$?)”
科幻

一个简单的例子,类似于上面的解决scheme。 这不需要监视任何过程输出。 下一个示例使用tail来跟踪输出。

 $ echo '#!/bin/bash' > tmp.sh $ echo 'sleep 30; exit 5' >> tmp.sh $ chmod +x tmp.sh $ ./tmp.sh & [1] 7454 $ pid=$! $ wait $pid [1]+ Exit 5 ./tmp.sh $ echo $? 5 

使用tail来跟踪过程输出并在过程完成时退出。

 $ echo '#!/bin/bash' > tmp.sh $ echo 'i=0; while let "$i < 10"; do sleep 5; echo "$i"; let i=$i+1; done; exit 5;' >> tmp.sh $ chmod +x tmp.sh $ ./tmp.sh 0 1 2 ^C $ ./tmp.sh > /tmp/tmp.log 2>&1 & [1] 7673 $ pid=$! $ tail -f --pid $pid /tmp/tmp.log 0 1 2 3 4 5 6 7 8 9 [1]+ Exit 5 ./tmp.sh > /tmp/tmp.log 2>&1 $ wait $pid $ echo $? 5 

背景subprocess的PID存储在$! 。 您可以将所有subprocess的pid存储到数组中,例如PIDS []

 wait [-n] [jobspec or pid …] 

等到每个进程标识pid或作业规范jobspec指定的subprocess退出并返回等待的最后一个命令的退出状态。 如果给出了工作规范,则等待工作中的所有进程。 如果没有给出参数,则等待所有当前活动的subprocess,返回状态为零。 如果提供-n选项,则等待任何作业终止并返回其退出状态。 如果jobspec和pid都不指定shell的活动subprocess,则返回状态为127。

使用wait命令可以等待所有的subprocess完成,同时你可以获得每个subprocess的退出状态和存储状态到STATUS []中 。 那么你可以根据地位做一些事情。

我已经尝试了下面的代码,它运行良好。

 #!/bin/bash # start 3 child processes concurrently, and store each pid into PIDS[]. i=0 process=(a.sh b.sh c.sh) for app in ${process[@]}; do ./${app} & pid=$! PIDS[$i]=${pid} ((i+=1)) done # wait for all processes to finish, and store each process's exit code into STATUS[]. i=0 for pid in ${PIDS[@]}; do echo "pid=${pid}" wait ${pid} STATUS[$i]=$? ((i+=1)) done # after all processed finish, check their exit codes in STATUS[]. i=0 for st in ${STATUS[@]}; do if [[ ${st} -ne 0 ]]; then echo "failed" else echo "finish" fi ((i+=1)) done 

另一个解决scheme是通过proc文件系统监视进程(比ps / grep更安全)。 当你启动一个进程时,它在/ proc / $ pid中有一个对应的文件夹,所以解决scheme可能是

 #!/bin/bash .... doSomething & local pid=$! while [ -d /proc/$pid ]; do # While directory exists, the process is running doSomethingElse .... else # when directory is removed from /proc, process has ended wait $pid local exit_status=$? done .... 

现在你可以使用$ exit_statusvariables,不过你喜欢。

这可能会超出你的问题,但是如果你关心进程运行的时间长度,你可能有兴趣在一段时间后检查运行后台进程的状态。 使用pgrep -P $$来检查哪些subprocess仍在运行是很容易的,但是我提出了以下解决scheme来检查那些已经过期的PID的退出状态:

 cmd1() { sleep 5; exit 24; } cmd2() { sleep 10; exit 0; } pids=() cmd1 & pids+=("$!") cmd2 & pids+=("$!") lasttimeout=0 for timeout in 2 7 11; do echo -n "interval-$timeout: " sleep $((timeout-lasttimeout)) # you can only wait on a pid once remainingpids=() for pid in ${pids[*]}; do if ! ps -p $pid >/dev/null ; then wait $pid echo -n "pid-$pid:exited($?); " else echo -n "pid-$pid:running; " remainingpids+=("$pid") fi done pids=( ${remainingpids[*]} ) lasttimeout=$timeout echo done 

其输出:

 interval-2: pid-28083:running; pid-28084:running; interval-7: pid-28083:exited(24); pid-28084:running; interval-11: pid-28084:exited(0); 

注意:您可以将$pids更改$pidsstringvariables而不是数组,以便简化操作。

用这种方法,你的脚本不必等待后台进程,你只需要监视一个临时文件的退出状态。

 FUNCmyCmd() { sleep 3;return 6; }; export retFile=$(mktemp); FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; FUNCexecAndWait& 

现在,你的脚本可以做任何事情,而你只需要继续监视retFile的内容(它也可以包含你想要的任何其他信息,如退出时间)。

PS:顺便说一句,我用bash编码思维