Linux 获取后台进程的退出代码
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1570262/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get exit code of a background process
提问by bob
I have a command CMD called from my main bourne shell script that takes forever.
我有一个从我的主 bourne shell 脚本调用的命令 CMD,它需要永远。
I want to modify the script as follows:
我想修改脚本如下:
- Run the command CMD in parallel as a background process ($CMD &).
- In the main script, have a loop to monitor the spawned command every few seconds. The loop also echoes some messages to stdout indicating progress of the script.
- Exit the loop when the spawned command terminates.
- Capture and report the exit code of the spawned process.
- 并行运行命令 CMD 作为后台进程 ($CMD &)。
- 在主脚本中,每隔几秒钟就有一个循环来监视生成的命令。该循环还会向 stdout 回显一些消息,指示脚本的进度。
- 当生成的命令终止时退出循环。
- 捕获并报告衍生进程的退出代码。
Can someone give me pointers to accomplish this?
有人可以给我指示来完成这个吗?
回答by Abu Aqil
#/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 $?
回答by mob
1: In bash, $!
holds the PID of the last background process that was executed. That will tell you what process to monitor, anyway.
1:在 bash 中,$!
保存最后执行的后台进程的 PID。无论如何,这将告诉您要监视的进程。
4: wait <n>
waits until the process with PID <n>
is complete (it will block until the process completes, so you might not want to call this until you are sure the process is done), and then returns the exit code of the completed process.
4:wait <n>
等待PID的进程<n>
完成(它会阻塞直到进程完成,所以你可能不想调用这个直到你确定进程完成),然后返回已完成进程的退出代码。
2, 3: ps
or ps | grep " $! "
can tell you whether the process is still running. It is up to you how to understand the output and decide how close it is to finishing. (ps | grep
isn't idiot-proof. If you have time you can come up with a more robust way to tell whether the process is still running).
2、3:ps
或者ps | grep " $! "
可以告诉你进程是否还在运行。如何理解输出并决定距离完成有多近取决于您。(ps | grep
不是防白痴。如果你有时间,你可以想出一种更可靠的方法来判断进程是否仍在运行)。
Here's a skeleton script:
这是一个骨架脚本:
# 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
# The variable $? always holds the exit code of the last command to finish.
# Here it holds the exit code of $my_pid, since wait exits with that code.
my_status=$?
echo The exit status of the process was $my_status
回答by William Pursell
I would change your approach slightly. Rather than checking every few seconds if the command is still alive and reporting a message, have another process that reports every few seconds that the command is still running and then kill that process when the command finishes. For example:
我会稍微改变你的方法。与其每隔几秒检查一次命令是否还活着并报告一条消息,不如让另一个进程每隔几秒报告一次命令仍在运行,然后在命令完成时终止该进程。例如:
#!/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
回答by Darren Weber
A simple example, similar to the solutions above. This doesn't require monitoring any process output. The next example uses tail to follow output.
一个简单的例子,类似于上面的解决方案。这不需要监视任何过程输出。下一个示例使用 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
Use tail to follow process output and quit when the process is complete.
使用 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
回答by errant.info
This may be extending beyond your question, however if you're concerned about the length of time processes are running for, you may be interested in checking the status of running background processes after an interval of time. It's easy enough to check which child PIDs are still running using pgrep -P $$
, however I came up with the following solution to check the exit status of those PIDs that have already expired:
这可能超出了您的问题,但是如果您担心进程运行的时间长度,您可能有兴趣在一段时间后检查运行后台进程的状态。使用 很容易检查哪些子 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
which outputs:
输出:
interval-2: pid-28083:running; pid-28084:running;
interval-7: pid-28083:exited(24); pid-28084:running;
interval-11: pid-28084:exited(0);
Note: You could change $pids
to a string variable rather than array to simplify things if you like.
注意:$pids
如果您愿意,您可以更改为字符串变量而不是数组以简化事情。
回答by Искрен Хаджинедев
Another solution is to monitor processes via the proc filesystem (safer than ps/grep combo); when you start a process it has a corresponding folder in /proc/$pid, so the solution could be
另一种解决方案是通过 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
....
Now you can use the $exit_status variable however you like.
现在您可以随意使用 $exit_status 变量。
回答by TrueY
As I see almost all answers use external utilities (mostly ps
) to poll the state of the background process. There is a more unixesh solution, catching the SIGCHLD signal. In the signal handler it has to be checked which child process was stopped. It can be done by kill -0 <PID>
built-in (universal) or checking the existence of /proc/<PID>
directory (Linux specific) or using the jobs
built-in (bashspecific. jobs -l
also reports the pid. In this case the 3rd field of the output can be Stopped|Running|Done|Exit . ).
正如我所看到的,几乎所有答案都使用外部实用程序(主要是ps
)来轮询后台进程的状态。有一个更 unixesh 的解决方案,捕获 SIGCHLD 信号。在信号处理程序中,必须检查哪个子进程被停止。它可以通过kill -0 <PID>
内置(通用)或检查/proc/<PID>
目录的存在(Linux 特定)或使用jobs
内置(bash特定。jobs -l
也报告 pid。在这种情况下,输出的第三个字段可以是 Stopped|Running |完成|退出。)。
Here is my example.
这是我的例子。
The launched process is called loop.sh
. It accepts -x
or a number as an argument. For -x
is exits with exit code 1. For a number it waits num*5 seconds. In every 5 seconds it prints its PID.
启动的进程称为loop.sh
。它接受-x
或 一个数字作为参数。For -x
is exits with exit code 1. 对于一个数字,它等待 num*5 秒。每 5 秒打印一次它的 PID。
The launcher process is called launch.sh
:
启动程序过程称为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
For more explanation see: Starting a process from bash script failed
有关更多解释,请参阅:从 bash 脚本启动进程失败
回答by Bjorn
This is how I solved it when I had a similar need:
当我有类似需求时,我是这样解决的:
# 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 finish, will take max 14s
# as it waits in order of launch, not order of finishing
for p in $pids; do
if wait $p; then
echo "Process $p success"
else
echo "Process $p fail"
fi
done
回答by Aquarius Power
With this method, your script doesnt have to wait for the background process, you will only have to monitor a temporary file for the exit status.
使用这种方法,您的脚本不必等待后台进程,您只需监视一个临时文件的退出状态。
FUNCmyCmd() { sleep 3;return 6; };
export retFile=$(mktemp);
FUNCexecAndWait() { FUNCmyCmd;echo $? >$retFile; };
FUNCexecAndWait&
now, your script can do anything else while you just have to keep monitoring the contents of retFile (it can also contain any other information you want like the exit time).
现在,您的脚本可以做任何其他事情,而您只需要继续监视 retFile 的内容(它还可以包含您想要的任何其他信息,例如退出时间)。
PS.: btw, I coded thinking in bash
PS.: btw, 我用 bash 编码思考
回答by Terry
The pid of a backgrounded child process is stored in $!. You can store all child processes' pids into an array, e.g. PIDS[].
后台子进程的 pid 存储在$! . 您可以将所有子进程的 pid 存储到一个数组中,例如PIDS[]。
wait [-n] [jobspec or pid …]
Wait until the child process specified by each process ID pid or job specification jobspec exits and return the exit status of the last command waited for. If a job spec is given, all processes in the job are waited for. If no arguments are given, all currently active child processes are waited for, and the return status is zero. If the -n option is supplied, wait waits for any job to terminate and returns its exit status. If neither jobspec nor pid specifies an active child process of the shell, the return status is 127.
等到每个进程 ID pid 或作业规范 jobspec 指定的子进程退出并返回上一个等待的命令的退出状态。如果给出了作业规范,则等待作业中的所有进程。如果没有给出参数,则等待所有当前活动的子进程,返回状态为零。如果提供了 -n 选项,则等待任何作业终止并返回其退出状态。如果 jobspec 和 pid 都没有指定 shell 的活动子进程,则返回状态为 127。
Use waitcommand you can wait for all child processes finish, meanwhile you can get exit status of each child processes via $?and store status into STATUS[]. Then you can do something depending by status.
使用wait命令可以等待所有子进程完成,同时可以通过$?获取每个子进程的退出状态。并将状态存储到STATUS[] 中。然后你可以根据状态做一些事情。
I have tried the following 2 solutions and they run well. solution01is more concise, while solution02is a little complicated.
我已经尝试了以下 2 种解决方案,它们运行良好。solution01更简洁,而solution02有点复杂。
solution01
解决方案01
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array PIDS[].
process=(a.sh b.sh c.sh)
for app in ${process[@]}; do
./${app} &
PIDS+=($!)
done
# wait for all processes to finish, and store each process's exit code into array STATUS[].
for pid in ${PIDS[@]}; do
echo "pid=${pid}"
wait ${pid}
STATUS+=($?)
done
# after all processed finish, check their exit codes in STATUS[].
i=0
for st in ${STATUS[@]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
else
echo "$i finish"
fi
((i+=1))
done
solution02
解决方案02
#!/bin/bash
# start 3 child processes concurrently, and store each pid into array 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 array 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 "$i failed"
else
echo "$i finish"
fi
((i+=1))
done