shell – 获取后台进程的退出代码
我有一个从我的主Bourne shell脚本调用命令CMD永远。
我想修改脚本如下:
- 并行运行命令CMD作为后台进程($ CMD&)。
- 在主脚本中,每隔几秒钟都有一个循环来监视生成的命令。 该循环还回显了一些消息到标准输出指示脚本的进展。
- 生成的命令终止时退出循环。
- 捕获并报告产生的进程的退出代码。
有人可以给我指针来完成这个?
1:在bash中, $!
保存最后执行的后台进程的PID。 无论如何,这将告诉你要监视的过程。
4: wait <n>
等待,直到具有ID的进程完成(它将阻塞,直到进程完成,所以你可能不想调用这个,直到你确定进程已经完成)。 wait
返回后,进程的退出代码返回variables$?
2,3: ps
或ps | 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
更改$pids
stringvariables而不是数组,以便简化操作。
用这种方法,你的脚本不必等待后台进程,你只需要监视一个临时文件的退出状态。
FUNCmyCmd() { sleep 3;return 6; }; export retFile=$(mktemp); FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; }; FUNCexecAndWait&
现在,你的脚本可以做任何事情,而你只需要继续监视retFile的内容(它也可以包含你想要的任何其他信息,如退出时间)。
PS:顺便说一句,我用bash编码思维