26

Say I have a pid in hand, mypid=$$

is there some bash/system command I can use to listen for the exit of that process with the given pid?

If no process with mypid exists, I guess the command should simply fail.

4
  • I don't use C#, but apparently there must be a way: msdn.microsoft.com/en-us/library/fb4aw7b8(v=vs.110).aspx Feb 28, 2018 at 7:41
  • In Unix, it's common to wait for child processes using wait in the shell or the wait() C library function. There is AFAIK no standard way of waiting for a non-child process. It is further unclear if the C# function can do that (it depends on what an "associated process" is).
    – Kusalananda
    Feb 28, 2018 at 7:46
  • I could do this with polling but that would be awful Feb 28, 2018 at 7:48
  • It would also potentially give you the wrong results. PID reuse may theoretically mean that a process could come alive with the same PID as the process you are waiting for. On Linux (with sequential PIDs) this would be unlikely, but on systems like OpenBSD (randomized PID allocation), it would be an issue.
    – Kusalananda
    Feb 28, 2018 at 7:52

4 Answers 4

53

I got what I needed from this answer: https://stackoverflow.com/a/41613532/1223975

..turns out using wait <pid> will only work if that pid is a child process of the current process.

However the following will work for any process:

To wait for any process to finish

Linux:

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)
9
  • 1
    It's not related to Linux, it's a feature of GNU tail. So it will work on any system with GNU tail like GNU/Linux ones, and it wouldn't work on non-GNU Linux-based systems. Feb 28, 2018 at 12:55
  • 1
    I fear that tail/lsof will use polling in this case, at least given the empirical evidence it appears that way on MacOS. Feb 28, 2018 at 16:18
  • lsof +r puts it in repeat mode, so that command is repeating every second so it's using polling, damn Feb 28, 2018 at 16:27
  • gnu tail is available to install via homebrew on macOS (Darwin), and becomes available as gtail. apple.stackexchange.com/a/69332. Is tail --pid preferable over lsof -p?
    – codepoet
    Aug 21, 2021 at 2:10
  • 1
    AWESOME!!! I was still resorting to the sleep loop, but this is precise, simple and, apparently, portable. Oct 17 at 17:14
5

The portable way is to poll using kill, i.e. something like:

until kill -s 0 "$pid" 2>/dev/null; do sleep 1; done

This doesn't require non-portable commands such as GNU tail or lsof, and, in bash, only invokes one external command, namely sleep. GNU tail is likely a bit more efficient, since it was written in C (and could, in theory, take advantage of advanced functions such as pidfd_open), and lsof is likely going to have a lot of overhead.

A rough timeout can be implemented by counting the number of loop iterations.

A slightly less portable solution that only works for one waiter is to use ptrace(2), which doesn't have a portable shell interface, but which can be accessed using the strace command on some systems:

strace -e exit -e signal=none -p "$pid"
3
  • See also until as an equivalent (and slightly more portable as ! is not Bourne) of while !. And kill -s 0 as the POSIX equivalent of POSIX+XSI kill -0. Dec 29, 2020 at 15:38
  • GNU tail is available as gtail after you install coreutils via homebrew (works even on M1 Mac): apple.stackexchange.com/a/69332. Are we sure if tail is much more efficient for the job compared to lsof? am not a unix/linux expert.
    – codepoet
    Aug 21, 2021 at 3:00
  • strace -p <PID> works for me. It even shows wait() with child process exit status.
    – Papillon
    Jul 6 at 3:09
2

You can use the bash builtin wait:

$ sleep 10 &
[2] 28751
$ wait 28751
[2]-  Done                    sleep 10
$ help wait
wait: wait [-n] [id ...]
    Wait for job completion and return exit status.

    Waits for each process identified by an ID, which may be a process ID or a
    job specification, and reports its termination status.  If ID is not
    given, waits for all currently active child processes, and the return
    status is zero.  If ID is a a job specification, waits for all processes
    in that job's pipeline.

    If the -n option is supplied, waits for the next job to terminate and
    returns its exit status.

    Exit Status:
    Returns the status of the last ID; fails if ID is invalid or an invalid
    option is given.

It uses the system call waitpid() ..

$ whatis waitpid
waitpid (2)          - wait for process to change state
4
  • nice that works for me, hopefully doesn't use polling under the hood, thanks! Feb 28, 2018 at 8:04
  • 5
    yeah won't work for my use case, I get this error: bash: wait: pid 47760 is not a child of this shell...back to the drawing board lol Feb 28, 2018 at 8:22
  • I have answer, I just posted it thx Feb 28, 2018 at 8:31
  • 5
    This will only wait for child processes, not for a process unassociated with the current process.
    – Kusalananda
    Feb 28, 2018 at 8:51
2

Regarding the https://stackoverflow.com/a/41613532/1223975 solution that Alexander Mills reposted, Timeout in Seconds Darwin, is a 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 also 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.

2
  • GNU tail is available as gtail after you install coreutils via homebrew (works even on M1 Mac): apple.stackexchange.com/a/69332. The answer unix.stackexchange.com/a/626632/456507 suggests that GNU tail will be more efficient compared to lsof for the job, though idk.
    – codepoet
    Aug 21, 2021 at 2:57
  • "a UNIX-like OS that does not have GNU tail" is not necessarily an Apple OS that has ready access to GNU tail built for it. This answer was written expressly for a situation where GNU tail was not available or a user didn't want to have to get it. A careful read of the answer shows that the goal of the answer was to supply a solution that had greater cross-platform compatibility.
    – kbulgrien
    Aug 24, 2021 at 2:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .