Get exit code of a background process
Solution 1
1: In bash, $!
holds the PID of the last background process that was executed. That will tell you what process to monitor, anyway.
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.
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).
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
Solution 2
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
Solution 3
The pid of a backgrounded child process is stored in $!. You can store all child processes' pids into an array, e.g. 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.
Use wait command 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.
I have tried the following 2 solutions and they run well. solution01 is more concise, while solution02 is a little complicated.
solution01
#!/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
#!/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
Solution 4
#/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 $?
Solution 5
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 (bash specific. jobs -l
also reports the pid. In this case the 3rd field of the output can be Stopped|Running|Done|Exit . ).
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.
The launcher process is called 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
Related videos on Youtube
bob
Updated on April 20, 2021Comments
-
bob about 3 years
I have a command CMD called from my main bourne shell script that takes forever.
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.
Can someone give me pointers to accomplish this?
-
TrueY almost 8 years...and the winner is?
-
ghoti almost 6 years@TrueY .. bob hasn't logged in since the day he asked the question. We're unlikely ever to know!
-
Gabriel Staples about 2 years
- Run the command CMD in parallel as a background process (
-
SourceSeeker over 14 yearsHere's a trick to avoid the
grep -v
. You can limit the search to the beginning of the line:grep '^'$pid
Plus, you can dops p $pid -o pid=
, anyway. Also,tail -f
isn't going to end until you kill it so I don't think it's a very good way to demo this (at least without pointing that out). You might want to redirect the output of yourps
command to/dev/null
or it'll go to the screen at every iteration. Yourexit
causes thewait
to be skipped - it should probably be abreak
. But aren't thewhile
/ps
and thewait
redundant? -
SourceSeeker over 14 years
ps -p $my_pid -o pid=
neithergrep
is needed. -
mob over 14 years@Dennis Williamson
ps
has many flavors. Your call doesn't work for me butps -p$my_pid
does. Your larger point thatgrep
isn't necessary is correct. -
mob over 14 yearsHmmm .. actually I can't figure out a good way to avoid grep on Cygwin.
ps -p$pid
always has exit status of 0 whether $pid exists or not. I could say something likewhile [ 'ps -p$pid | wc -l' \> 1 ]
but that's hardly an improvement ... -
ephemient over 14 yearsWhy does everybody forget about
kill -0 $pid
? It doesn't actually send any signal, only checks that the process is alive, using a shell built-in instead of external processes. -
ephemient over 14 years
kill -0 $!
is a better way of telling whether a process is still running. It doesn't actually send any signal, only checks that the process is alive, using a shell built-in instead of external processes. Asman 2 kill
says, "If sig is 0, then no signal is sent, but error checking is still performed; this can be used to check for the existence of a process ID or process group ID." -
errant.info almost 11 yearsBecause you can only kill a process you own:
bash: kill: (1) - Operation not permitted
-
Craig Ringer almost 11 years@ephemient
kill -0
will return non-zero if you don't have permission to send signals to a process that is running. Unfortunately it returns1
in both this case and the case where the process doesn't exist. This makes it useful unless you don't own the process - which can be the case even for processes you created if a tool likesudo
is involved or if they're setuid (and possibly drop privs). -
nmax over 9 yearsif you only need to monitor a process and not a specific PID, pgrep is also useful: pgrep -x [process]
-
DifferentPseudonym almost 9 years
wait
doesn't return the exit code in the variable$?
. It just returns the exit code and$?
is the exit code of the latest foreground program. -
Trevor Boyd Smith almost 9 yearsFor the many people voting up the
kill -0
. Here is a peer reviewed reference from SO that shows CraigRinger's comment is legit regarding:kill -0
will return non-zero for running processes... butps -p
will always return 0 for any running process. -
Brais Gabin over 8 yearsThe loop is redundant. Just wait. Less code => less edge cases.
-
jarno about 8 years@mob The current while condition does not work here (in Ubuntu linux). Can't you use
-o pid=
for ps (to disable the header)? Thenwhile [ -n "$(ps -o pid= -p$my_pid)" ]
would work; here it works also without-p
.while [ $(ps -p$pid | wc -l) \> 1 ]
works, too, but apparently you did not like it. -
mob about 8 yearsThere are many different flavors of
ps
. You may have to experiment with options that work for your system and your configuration. -
benjaoming about 8 yearsDoesn't work in bash?
Syntax error: "else" unexpected (expecting "done")
-
Terry over 6 yearsI have tried and proved it runs well. You can read my explaination in code.
-
Noel Widmer over 6 yearsPlease read "How do I write a good answer?" where you'll find the following info: ... try to mention any limitations, assumptions or simplifications in your answer. Brevity is acceptable, but fuller explanations are better. You answer is therefore acceptable but you have much better chances of getting upvotes if you can elaborate on the problem and your solution. :-)
-
Luke Davis over 6 yearsThanks! This one seems to me the simplest approach.
-
Dima Korobskiy over 6 yearsThis solution doesn't satisfy requirement #2: a monitoring loop per background process.
wait
s cause script to wait until the very end of (each) process. -
Dima Korobskiy over 6 years@Brais Gabin The monitoring loop is requirement #2 of the question
-
Santosh Kumar Arjunan about 6 yearsSimple and nice approach.. have been searching for this solution quite sometime..
-
PlasmaBinturong about 6 yearsSince we are talking about Bash, the for loop could have been written as:
for i in ${!pids[@]};
using parameter expansion. -
codeforester almost 6 years
pid=$!; PIDS[$i]=${pid}; ((i+=1))
can be written more simply asPIDS+=($!)
which simply appends to the array without having to use a separate variable for indexing or the pid itself. The same thing applies to theSTATUS
array. -
Terry almost 6 years@codeforester, thank you for your sugesstion, I have modifed my inital code into solution01, it looks more concise.
-
codeforester almost 6 yearsThe same thing applies to other places where you are adding things into an array.
-
Terry over 5 years@Wernfried Domscheit 1. If b.sh or c.sh have been finished before a.sh, wait [pid of a.sh] still running. until wait [pid of a.sh] finishs, the program will run next step. 2. In my linux, it doesn't happen that wait [pid of b.sh] will return "-bash: wait: pid xxx is not a child of this shell", could you give me some detail on how to reproduce this case ? thank you
-
Wernfried Domscheit over 5 yearsIt was my mistake, I tried
wait [random number]
instead of an existing PID -
conny over 5 yearsThis doesn't work .. or doesn't do what you want: it doesn't check backgrounded processes exit statuses?
-
Bjorn over 5 years@conny, yes it does check the exit status of the backgrounded processes. The "wait" command returns the exit status of the processes. In the example shown here it is demonstrated by the "Process $p success/fail".
-
punkbit almost 5 yearsgreat template, thanks for sharing! I believe that instead of trap, we can also do
while kill -0 $pid 2> /dev/null; do X; done
, hope it's useful for someone else in the future who reads this message ;) -
Troels Ynddal almost 2 yearsI think
${#PID}
should be${#PID[@]}