Normally, when a job is launched in the background, jobs
will report that it is finished the first time it is run after the job's completion, and nothing for subsequent executions:
$ ping -c 4 localhost &>/dev/null &
[1] 9666
$ jobs
[1]+ Running ping -c 4 localhost &> /dev/null &
$ jobs
[1]+ Done ping -c 4 localhost &> /dev/null
$ jobs ## returns nothing
$
However, when run in a subshell within a script it seems to always return a value. This script will never exit:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
sleep 1;
done
If I use tee
in the [[ ]]
construct to see the output of jobs
, I see that it is always printing the Done ...
line. Not only once as I expected but, apparently, for ever.
What is even stranger is that running jobs
within the loop causes it to exit as expected:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
jobs
sleep 1;
done
Finally, as pointed out by @mury, the first script works as expected and exits if run from the commandline:
$ ping -c 5 localhost &>/dev/null &
[1] 13703
$ while [[ -n $(jobs) ]]; do echo -n . ; sleep 1; done
...[1]+ Done ping -c 5 localhost &> /dev/null
$
This came up when I was answering a question on Super User so please don't post answers recommending better ways of doing what that loop does. I can think of a few myself. What I am curious about is
Why does
jobs
act differently within the[[ ]]
construct? Why will it always return theDone...
line while it doesn't when run manually?Why does running
jobs
within the loop change the behavior of the script?
while [[ -n $(jobs) ]] ..
in an interactive shell, it worked fine.jobs
in the loop fix it? Also, it works as expected even in a script if it is not run in a subshell. If you save the output in a variable and test that variable, for example.jobs
and why it behaves this way in this setting.