Wait for a process to finish
Asked Answered
H

15

217

Is there any builtin feature in Bash to wait for a process to finish?

The wait command only allows one to wait for child processes to finish. I would like to know if there is any way to wait for any process to finish before proceeding in any script.

A mechanical way to do this is as follows but I would like to know if there is any builtin feature in Bash.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Honky answered 29/6, 2009 at 12:33 Comment(2)
Let me give two cautions: 1. As pointed out below by mp3foley, "kill -0" does not always work for POSIX. 2. Probably you also want to make sure that the process is not a zombie, which is virtually a terminated process. See mp3foley's comment and mine for the details.Mosher
Another caution (originally pointed out by ks1322 below): Using PID other than a child process is not robust. If you want a secure way, use e.g. IPC.Mosher
B
278

To wait for any process to finish

Linux (doesn't work on Alpine, where ash doesn't support tail --pid):

tail --pid=$pid -f /dev/null

Darwin (requires that $pid has open files):

lsof -p $pid +r 1 &>/dev/null

With timeout (seconds)

Linux:

timeout $timeout tail --pid=$pid -f /dev/null

Darwin (requires that $pid has open files):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
Bundesrat answered 12/1, 2017 at 12:41 Comment(11)
Who would have known that tail would do this.Bashemath
The linux solution appears to work for Cygwin and Solaris as well, based on a brief test with sleep (although the pwait solution below for Solaris is easier)Danieu
tail works under the hood by polling with kill(pid, SIG_0) to a process (discovered using strace).Proportionable
Note that lsof uses polling, that +r 1 is the timeout, I am personally looking for a solution for MacOS that does not use polling.Dellora
This trick fails for zombies. It's ok for processes you can't kill; tail has the line kill (pid, 0) != 0 && errno != EPERM.Mosher
@AlexanderMills, if you can tolerate your macOS system not going to sleep as the command executes, caffeinate -w $pid will do the trick.Rockie
Also bear in mind that for docker implementations like busybox, --pid has not been implemented in tail.Specify
tail --pid is likely a GNU-ism.Sissie
my current version of macOS 10.14.6 (Mojave) supports tail --pid=$pid -f /dev/nullCamera
Can we wait for multiple pids somehow?Fop
tail --pid=$pid1 --pid=$pid2 -f /dev/nullBundesrat
F
95

There's no builtin. Use kill -0 in a loop for a workable solution:

anywait(){

    for pid in "$@"; do
        while kill -0 "$pid"; do
            sleep 0.5
        done
    done
}

Or as a simpler oneliner for easy one time usage:

while kill -0 PIDS 2> /dev/null; do sleep 1; done;

As noted by several commentators, if you want to wait for processes that you do not have the privilege to send signals to, you have find some other way to detect if the process is running to replace the kill -0 $pid call. On Linux, test -d "/proc/$pid" works, on other systems you might have to use pgrep (if available) or something like ps | grep "^$pid ".

Freestanding answered 14/7, 2009 at 20:6 Comment(11)
Never found better. you can add a timeout parameter that kill -15 then kill -9 if the process get stuck ...Protagoras
Better yet, kill -15, kill -2, kill -1, then maybe kill -9.Septarium
Caution: This does not always work, as pointed out below by mp3foley. See that comment and mine for the details.Mosher
Caution 2 (On zombies): The follow-up comment by Teddy above is not sufficient yet, since they may be zombies. See my answer below for a Linux solution.Mosher
Doesn't this solution risk a race condition? While sleeping in sleep 0.5, process with $pid may die and another process may be created with the same $pid. And we will end up waiting for 2 different processes (or even more) with the same $pid.Tilefish
@Tilefish Yes, this code indeed has a race condition in it.Freestanding
Aren't PIDs usually sequentially generated? What is the likeliness of the count wrapping around in one second?Encephalomyelitis
@Encephalomyelitis depends on how busy the system is and /proc/sys/kernel/pid_max, but yes normally unlikely. The bigger problem is if you are waiting for multiple pids, and by the time the first pid has been waited for, the second/later pids are replaced. For multiple pids you probably want multiple async waits, that are all waited for.Demo
Note that kill is a shell builtin.Seaweed
downvote because it doesn't always work, as indicated in comments.Sissie
You can work around the race condition by checking the process's start time. If the start time is the same, the pid must refer to the same process.Blank
H
57

I found "kill -0" does not work if the process is owned by root (or other), so I used pgrep and came up with:

while pgrep -u root process_name > /dev/null; do sleep 1; done

This would have the disadvantage of probably matching zombie processes.

Headrace answered 2/5, 2012 at 4:52 Comment(1)
Good observation. In POSIX, the system call kill(pid, sig=0) fails if the caller process doesn't have a privilege to kill. Thus /bin/kill -0 and "kill -0" (bash built-in) fail too under the same condition.Mosher
M
33

This bash script loop ends if the process does not exist, or it's a zombie.

PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
    sleep 1
done

EDIT: The above script was given below by Rockallite. Thanks!

My orignal answer below works for Linux, relying on procfs i.e. /proc/. I don't know its portability:

while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
    sleep 1
done

It's not limited to shell, but OS's themselves do not have system calls to watch non-child process termination.

Mosher answered 30/7, 2012 at 10:48 Comment(4)
Nice one. Although I had to surround grep /proc/$PID/status with double quotes (bash: test: argument expected)Illuminance
Hum ... just tried it again and it worked. I guess I did something wrong last time.Illuminance
Or while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; doneEbonieebonite
Unfortunately, this doesn't work in BusyBox - in its ps, neither -p or s= are supportedJennefer
P
16

FreeBSD and Solaris have this handy pwait(1) utility, which does exactly, what you want.

I believe, other modern OSes also have the necessary system calls too (MacOS, for example, implements BSD's kqueue), but not all make it available from command-line.

Prototherian answered 21/1, 2015 at 16:0 Comment(3)
> BSD and Solaris: Inspecting the three big BSDs that come to mind; neither OpenBSD nor NetBSD have this function (in their man pages), only FreeBSD does, as you can easily check on man.openbsd.org.Gagne
Seems like you are right. Mea culpa... They all implement kqueue, so compiling FreeBSD's pwait(1) would be trivial, however. Why wouldn't the other BSD's import the feature escapes me...Prototherian
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etc just allowed me to go home now instead of hours from now.Danieu
A
12

From the bash manpage

   wait [n ...]
          Wait for each specified process and return its termination  status
          Each  n  may be a process ID or a job specification; if a
          job spec is given, all processes  in  that  job's  pipeline  are
          waited  for.  If n is not given, all currently active child processes
          are waited for, and the return  status  is  zero.   If  n
          specifies  a  non-existent  process or job, the return status is
          127.  Otherwise, the return status is the  exit  status  of  the
          last process or job waited for.
Auteur answered 26/8, 2011 at 20:8 Comment(4)
That's true but it can only wait for child of the current shell. You can't wait for any process.Preponderate
@Preponderate : "If n is not given, all currently active child processes are waited for" . This works perfectly..wait without args will block the process until any child processes finish. Honestly, I don't see any point to wait for any process since there's always systemprocesses going on.Biegel
@Biegel ( sleep 10 & sleep 3 & wait ) takes 10 seconds to return: wait without args will block until ALL child processes finish. OP wants to be notified when the first child (or nominated) process finishes.Baseburner
Also doesn't work if the process isn't backgrounded or foregrounded (on Solaris, Linux, or Cygwin). ex. sleep 1000 ctrl-z wait [sleep pid] returns immediatelyDanieu
I
7

All these solutions are tested in Ubuntu 14.04:

Solution 1 (by using ps command): Just to add up to Pierz answer, I would suggest:

while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done

In this case, grep -vw grep ensures that grep matches only process_name and not grep itself. It has the advantage of supporting the cases where the process_name is not at the end of a line at ps axg.

Solution 2 (by using top command and process name):

while [[ $(awk '$12=="process_name" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Replace process_name with the process name that appears in top -n 1 -b. Please keep the quotation marks.

To see the list of processes that you wait for them to be finished, you can run:

while : ; do p=$(awk '$12=="process_name" {print $0}' <(top -n 1 -b)); [[ $b ]] || break; echo $p; sleep 1; done

Solution 3 (by using top command and process ID):

while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Replace process_id with the process ID of your program.

Intellectualism answered 23/4, 2015 at 11:26 Comment(2)
Downvote: the long grep -v grep pipeline is a massive antipattern, and this presupposes that you don't have unrelated processes with the same name. If you know the PIDs instead, this could be adapted to a properly working solution.Spiritual
Thanks tripleee for the comment. I added the flag -w to avoid the problem of grep -v grep to some extent. I also added two more solutions based on your comment.Intellectualism
A
6

Okay, so it seems the answer is -- no, there is no built in tool.

After setting /proc/sys/kernel/yama/ptrace_scope to 0, it is possible to use the strace program. Further switches can be used to make it silent, so that it really waits passively:

strace -qqe '' -p <PID>
Amaranth answered 6/4, 2014 at 11:34 Comment(3)
Nice one! It seems though that it is not possible to attach to a given PID from two different places (I get Operation not permitted for the second strace instance); can you confirm that?Macymad
@Macymad Yes, the manpage for ptrace says: (...)"tracee" always means "(one) thread" (and I confirm the error you mention). To let more processes wait this way, you'd have to make a chain.Amaranth
I don't have the ptrace_scope parameter (kernel 4.14) but this is also a busy-wait solution. From looking at the source I cannot even see a delay built in the loop (but the process does use very little CPU).Kabuki
P
4

Had the same issue, I solved the issue killing the process and then waiting for each process to finish using the PROC filesystem:

while [ -e /proc/${pid} ]; do sleep 0.1; done
Pence answered 14/6, 2017 at 11:3 Comment(1)
polling is very bad, you could get a visit from the police :)Dellora
V
3

There is no builtin feature to wait for any process to finish.

You could send kill -0 to any PID found, so you don't get puzzled by zombies and stuff that will still be visible in ps (while still retrieving the PID list using ps).

Vivid answered 29/6, 2009 at 17:19 Comment(0)
S
3

Blocking solution

Use the wait in a loop, for waiting for terminate all processes:

function anywait()
{

    for pid in "$@"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

This function will exits immediately, when all processes was terminated. This is the most efficient solution.

Non-blocking solution

Use the kill -0 in a loop, for waiting for terminate all processes + do anything between checks:

function anywait_w_status()
{
    for pid in "$@"
    do
        while kill -0 "$pid"
        do
            echo "Process $pid still running..."
            sleep 1
        done
    done
    echo 'All processes terminated'
}

The reaction time decreased to sleep time, because have to prevent high CPU usage.

A realistic usage:

Waiting for terminate all processes + inform user about all running PIDs.

function anywait_w_status2()
{
    while true
    do
        alive_pids=()
        for pid in "$@"
        do
            kill -0 "$pid" 2>/dev/null \
                && alive_pids+="$pid "
        done

        if [ ${#alive_pids[@]} -eq 0 ]
        then
            break
        fi

        echo "Process(es) still running... ${alive_pids[@]}"
        sleep 1
    done
    echo 'All processes terminated'
}

Notes

These functions getting PIDs via arguments by $@ as BASH array.

Stablish answered 8/9, 2015 at 4:28 Comment(0)
D
2

If you need to both kill a process and wait for it finish, this can be achieved with killall(1) (based on process names), and start-stop-daemon(8) (based on a pidfile).

To kill all processes matching someproc and wait for them to die:

killall someproc --wait             # wait forever until matching processes die
timeout 10s killall someproc --wait # timeout after 10 seconds

(Unfortunately, there's no direct equivalent of --wait with kill for a specific pid).

To kill a process based on a pidfile /var/run/someproc.pid using signal SIGINT, while waiting for it to finish, with SIGKILL being sent after 20 seconds of timeout, use:

start-stop-daemon --stop --signal INT --retry 20 --pidfile /var/run/someproc.pid
Drucilla answered 24/9, 2022 at 13:54 Comment(1)
You need to install psmisc if killall not found.Gwenny
B
1

Use inotifywait to monitor some file that gets closed, when your process terminates. Example (on Linux):

yourproc >logfile.log & disown
inotifywait -q -e close logfile.log

-e specifies the event to wait for, -q means minimal output only on termination. In this case it will be:

logfile.log CLOSE_WRITE,CLOSE

A single wait command can be used to wait for multiple processes:

yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log

The output string of inotifywait will tell you, which process terminated. This only works with 'real' files, not with something in /proc/

Betthezul answered 28/12, 2019 at 19:51 Comment(0)
M
1

Rauno Palosaari's solution for Timeout in Seconds Darwin, is an excellent workaround for a UNIX-like OS that does not have GNU tail (it is not specific to Darwin). But, depending on the age of the UNIX-like operating system, the command-line offered is more complex than necessary, and can fail:

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)

On at least one old UNIX, the lsof argument +r 1m%s fails (even for a superuser):

lsof: can't read kernel name list.

The m%s is an output format specification. A simpler post-processor does not require it. For example, the following command waits on PID 5959 for up to five seconds:

lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'

In this example, if PID 5959 exits of its own accord before the five seconds elapses, ${?} is 0. If not ${?} returns 1 after five seconds.

It may be worth expressly noting that in +r 1, the 1 is the poll interval (in seconds), so it may be changed to suit the situation.

Misrule answered 17/4, 2020 at 18:51 Comment(0)
M
-1

On a system like OSX you might not have pgrep so you can try this appraoch, when looking for processes by name:

while ps axg | grep process_name$ > /dev/null; do sleep 1; done

The $ symbol at the end of the process name ensures that grep matches only process_name to the end of line in the ps output and not itself.

Maneating answered 3/2, 2015 at 13:48 Comment(2)
Horrible: There may be multiple processes with that name somewhere in the command line, your own grep included. Instead of redirecting to /dev/null, -q should be used with grep. Another instance of the process may have started while your loop was sleeping and you'll never know...Prototherian
I'm not sure why you singled out this answer as 'Horrible' - as a similar approach has been suggested by others? Whilst the -q suggestion is valid, as I mentioned in my answer specifically the terminating $ means grep will not match the name "somewhere in the command line" nor will it match itself. Have u actually tried it on OSX?Maneating

© 2022 - 2024 — McMap. All rights reserved.