2009-10-15 46 views
86

我有一个命令CMD从我的主Bourne shell脚本中调用,需要永久使用。shell - 获取后台进程的退出代码

我想修改脚本如下:

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

有人能给我指针来完成这个吗?

+1

...而获胜者是? – TrueY 2016-06-07 21:19:43

回答

7
#/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 $? 
+2

这是避免'grep -v'的一个技巧。您可以将搜索限制在行首:'grep'^'$ pid'另外,无论如何,您都可以执行'ps p $ pid -o pid ='。另外,'tail -f'不会结束,除非你杀了它,所以我认为这不是一个很好的演示方法(至少没有指出)。您可能希望将您的'ps'命令的输出重定向到'/ dev/null',否则它会在每次迭代时进入屏幕。你的'exit'会导致'wait'被跳过 - 它应该是一个'break'。但是不是'while' /'ps'和'wait'冗余吗? – 2009-10-15 06:40:00

+5

为什么每个人都忘记'kill -0 $ pid'?它实际上并没有发送任何信号,只使用内置的shell而不是外部进程来检查进程是否活着。 – ephemient 2009-10-17 00:17:01

+1

因为你只能杀死你拥有的进程:'bash:kill:(1) - 不允许的操作' – 2013-05-02 03:16:41

89

1:在bash中,$!包含执行的最后一个后台进程的PID。无论如何,这将告诉你要监控的过程。

4:wait <n>一直等到具有ID的进程完成(它会阻塞,直到进程完成,因此您可能不想调用此过程直到确定进程已完成)。在wait返回后,过程的退出代码返回变量$?

2,3:psps | 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 
+10

的需求#2'ps -p $ my_pid -o pid ='既不需要grep也不需要。 – 2009-10-15 06:43:01

+1

@丹尼威廉姆森'ps'有很多口味。你的电话不适合我,但是'ps -p $ my_pid'。你的大点'grep'没有必要是正确的。 – mob 2009-10-15 16:01:10

+0

嗯..其实我找不出一个避免greg在Cygwin上的好方法。无论$ pid是否存在,'ps -p $ pid'的退出状态总是为0。我可以说'while'['ps -p $ pid |') wc -l'\> 1]'但这并不是什么改进...... – mob 2009-10-15 17:06:06

4

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

 
#!/bin/sh 

cmd() { sleep 5; exit 24; } 

cmd & # Run the long running process 
pid=$! # Record the pid 

# Spawn a process that coninually reports that the command is still running 
while echo "$(date): $pid is still running"; do sleep 1; done & 
echoer=$! 

# Set a trap to kill the reporter when the process finishes 
trap 'kill $echoer' 0 

# Wait for the process to finish 
if wait $pid; then 
    echo "cmd succeeded" 
else 
    echo "cmd FAILED!! (returned $?)" 
fi 
2

一个简单的例子,类似于上面的解决方案。这不需要监视任何过程输出。下一个示例使用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 

使用尾部跟随过程输出,当该过程完成后退出。

$ 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 
0

这可能超出你的问题,但是如果你担心的进程运行时间的长短,你可能有兴趣在一个时间间隔后,检查正在运行的后台进程的状态。这是很容易检查哪些孩子的PID使用pgrep -P $$仍在运行,但是,我想出了以下解决方案来检查已经过期的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更改为字符串变量而非数组以简化操作。

1

另一种解决方案是通过proc文件系统监视进程(比ps/grep组合安全);当你启动一个进程它在/ proc/$ PID相应的文件夹,因此该解决方案可能是

#!/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_STATUS变量,只要你喜欢。

+0

在bash中不起作用? ''语法错误:“其他”意外(期待“完成”)' – benjaoming 2016-02-10 11:21:40

6

正如我所看到的几乎所有答案都使用外部实用程序(主要是ps)来轮询后台进程的状态。还有一个更加unixesh的解决方案,捕获SIGCHLD信号。在信号处理程序中,必须检查哪个子进程已停止。它可以通过内置的(通用的)kill -0 <PID>或者检查/proc/<PID>目录(Linux专用)的存在或使用jobs内置的(特定。jobs -l也可以报告pid。在这种情况下,输出的第3个字段可以被停止|正在运行|完成|退出)。

这是我的例子。

启动的过程被称为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 

更多解释见:Starting a process from bash script failed

+0

因为我们在谈论Bash,for循环可能写成:'for i in $ {!pids [@]};'使用参数扩展。 – PlasmaBinturong 2018-02-22 10:19:53

31

这是我如何解决它的时候我也有类似的需要:

# 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 
+0

我喜欢这种方法。 – 2017-07-06 17:43:28

+0

谢谢!这在我看来是最简单的方法。 – 2017-09-26 20:20:19

+0

解决问题的一个非常好的方法! – 2017-11-30 13:53:50

0

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

FUNCmyCmd() { sleep 3;return 6; }; 

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

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

PS:顺便说一句,我编写的思维在bash

2

一转到后台子进程的PID存储在$!。 您可以将所有子进程的pid存储到一个数组中,例如PIDS []

wait [-n] [jobspec or pid …] 

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

使用等待命令您可以等待所有子进程完成,同时您可以获取每个子进程和存储的退出状态状态变成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 
+0

我试过并证明它运行良好。你可以在代码中阅读我的解释。 – 2017-09-14 07:44:24

+0

请阅读“我如何写一个好的答案?”(https://stackoverflow.com/help/how-to-answer)“,您将在其中找到以下信息:** ...尝试提及任何您的答案中的限制,假设或简化。简洁是可以接受的,但更全面的解释是更好的。**你的回答是可以接受的,但如果你能详细说明问题和解决方案,你有更好的机会获得提升。 :-) – 2017-09-14 08:05:34

1

我们的团队与远程SSH执行的脚本有相同的需求,该脚本在静止25分钟后超时。这是监控循环每秒检查后台进程的解决方案,但每10分钟打印一次以抑制不活动超时。

long_running.sh & 
pid=$! 

# Wait on a background job completion. Query status every 10 minutes. 
declare -i elapsed=0 
# `ps -p ${pid}` works on macOS and CentOS. On both OSes `ps ${pid}` works as well. 
while ps -p ${pid} >/dev/null; do 
    sleep 1 
    if ((++elapsed % 600 == 0)); then 
    echo "Waiting for the completion of the main script. $((elapsed/60))m and counting ..." 
    fi 
done 

# Return the exit code of the terminated background process. This works in Bash 4.4 despite what Bash docs say: 
# "If neither jobspec nor pid specifies an active child process of the shell, the return status is 127." 
wait ${pid}