14

What is the significance of the bash pattern [...] & wait $!? For example:

curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $!

I understand this as:

  1. The & instructs bash to run the prior pipeline in a background subshell.
  2. The wait $! then waits for the pipeline to finish before returning.

But if that's the case, how is it any different from just running the pipeline itself? What's the point?

The script containing this example (and a few other instances) can be found at:

https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker?view=azure-devops

3
  • 6
    Probably written by someone who typically uses another language where you fork off and wait. They just copied that idiom to Bash without thinking. You see that sort of thing all the time with other idioms (e.g., people asking how to loop over all the lines in a file and do something instead of just using sed or similar on the file directly, because looping over the lines is what they'd do in Python or C)
    – muru
    Commented Jun 7, 2022 at 3:33
  • 1
    It seems to be unnecessary & I think user @muru is right ; but one vague use might be like this :: When you want to monitor these Scripts, some other script can check "ps" output , see the "wait" command and say that "Script is waiting for long running commands" , else it can either say "all ok" or "Script stuck" , based on some "Progress Indicators" !
    – Prem
    Commented Jun 7, 2022 at 4:12
  • 3
    @Prem, though wait is (and has to be) a builtin command, so you can't see it in the output of ps.
    – ilkkachu
    Commented Jun 7, 2022 at 11:57

1 Answer 1

30

There are two side effects of running a command asynchronously in a non-interactive shell that make A and A & wait "$!" different:

  • SIGINT and SIGQUIT are ignored for A. If you press Ctrl+C, whilst running bash -c 'sleep 1000 & wait "$!"', you'll notice bash is terminated, but not sleep.

  • A's stdin is redirected to /dev/null:

    $ bash -c 'readlink /dev/fd/0'
    /dev/pts/4
    $ bash -c 'readlink /dev/fd/0 & wait "$!"'
    /dev/null
    

Another difference is that in A | B & wait "$!" (where $! contains the pid of the process eventually running B), the exit status of A is lost ($PIPESTATUS will contain only one entry, the exit status of wait, which will be that of B if the pipefail option is not set).

In zsh, in A | B, zsh waits for both A and B, but in A | B & wait $! only waits for B (and the exit status of A is lost even with pipefail).

Another difference is that if a signal is delivered to the shell, it will be handled straight away and wait will return (even if the process it's waiting for is not finished).

Compare:

$ bash -c 'trap "echo Ouch" TERM; sleep 10 & wait "$!"; echo "$?"' & sleep 1; kill "$!"
[1] 13749
Ouch
143

Where Ouch is output as soon as SIGTERM is sent to bash (and sleep 10 carries on running after bash terminates) with:

$ bash -c 'trap "echo Ouch" TERM; sleep 10; echo "$?"' & sleep 1; kill "$!"
[1] 13822
$ Ouch                                                                                                                                        6:11
0

[1]  + done       bash -c 'trap "echo Ouch" TERM; sleep 10; echo "$?"'

Where Ouch is output only after sleep 10 returns.

Whether any of those side effects were wanted by the script author is another matter. You'll notice many other mistakes in that script, it's very well possible that whoever wrote that script didn't know much about shell scripting.

You must log in to answer this question.

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