bash - how to get return values from functions that are called asynchronously

Viewed 652

I've got a bash script that calls from main module two functions asynchronously like this:

(func1 && print_msg f1) &
(func2 && print_msg f2) &

[...]

wait $(jobs -p)

How to return a value from func1 and func2 is easy and has often been discussed. My problem is how to access these return values inside the main module to check for specific values.

1 Answers

If you refer to the exit status as function's "return value", then it's easy with wait. But if your function returns something more complex than 8-bit integer, and a lot happens between putting the task to background, and the return value retrieval, read on.

The simple case

The wait built-in you already use, returns the exit status of the background process it waited on (but only if PID was explicitly given):

wait [..] 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.

For example:

$ f() { sleep 5; return 13; }
$ f &
$ wait $!
$ echo $?
13

A little bit more robust synchronization

The previous example is perfectly safe, since $! always returns PID of the last background process in the current shell, and nothing happens between f & and wait.

In the scenario with some work in between:

f &
pid=$!
# work
wait "$pid"
ret=$?

there exists a slight chance that f's PID (stored in pid) will be recycled -- in case more than pid_max processes (defaults to 32768 on Linux) are spawned during work section and some process spawned in work section receives the same PID (as pid). Until that happens -- and because bash caches exit statuses of all of its children -- the exit status of f will be tracked, stored and available, even if f terminates before you call wait "$pid".

If you have many (and/or long-lasting) processes (possibly in a loop) inside the work section above, you might want to use a more robust synchronization mechanism, or some form of IPC, perhaps named pipes/FIFOs (easily set-up with coproc).

FIFO can deliver the result, but also signal the background job completion, so you can use it instead of wait:

# fifo setup/clean-up
tmp=$(mktemp -td)
mkfifo "$tmp/f"
trap 'rm -rf "$tmp"' EXIT

# async f, push result to fifo
f() { sleep 5; echo "result" >"$tmp/f"; }
f &

# work

# block until f finishes, read result from fifo
f_ret=$(<"$tmp/f")
echo "f done, returned: $f_ret"
Related