2

I'm relatively new to bash scripts and have a question about something that seams strange to me.

I have written this as part of one of my bash scripts:

sleep 10 &
while [[ $(jobs) ]]
do
        sleep 1
        echo test
        jobs &> /dev/null
done

Why is the "jobs" line necessary? If I leave it out it will run forever. but if I run:

sleep 10 & while [[ $(jobs) ]]; do sleep 1; echo test; done

directly from a bash It works. I have tried this on multiple systems.

pt1997
  • 31
  • 4
  • turn on shell debugging/trace with `set -vx` (and off with `set +vx`) inside your script to understand what code is being executed, what env_vars values are being used (none in your sample above). I think the `jobs &> /dev/null` ls hiding/duplicating stuff and could cause problems. Search here for `[bash] jobs -cron`. https://stackoverflow.com/questions/1537956/bash-limit-the-number-of-concurrent-jobs looks like it could help you. Good luck. – shellter Sep 13 '19 at 19:30
  • 1
    Isn't the `wait` command made for this? – Benjamin W. Sep 13 '19 at 19:53
  • @shellter Thank you, the "set -vx" option showed me that the jobs command is still containing the value "Done" if it is just run from within the while check and therefor that turns true, but if it is run by the "jobs" line it is losing that value, still strange, but that explains it. – pt1997 Sep 13 '19 at 19:56

2 Answers2

3

Fun observation.

This happens because one feature of jobs is to show a status when a job has terminated, but only if you haven't already been notified:

$ true & sleep 1; jobs;
[1] 18687
[1]+  Done                    true

It would be annoying and unhelpful if you saw "[1]+ Done" every single time you ever ran jobs again in that shell. Therefore, jobs will also clean up the job table so that a second jobs won't notify you again:

$ true & sleep 1; jobs; echo "Here's the output the second time:"; jobs
[1] 18694
[1]+  Done                    true
Here's the output the second time:
(nothing)

Now, when you run $(jobs), you create a subshell. Due to how the Unix programming model works, $(jobs) is implemented by forking an identical copy of the current process (a subshell) with stdout connected to a pipe, let that process run jobs, and then read the result from the pipe.

This means that any effect jobs has on the jobs table is restricted to the subshell. The parent doesn't realize you've already seen the "Done" message.

Every time you run [[ $(jobs) ]], it'll therefore show you the "Done" message, so that the statement is true.

Adding a jobs &> /dev/null will cause the parent to update its own jobs table instead of just the subshell's table, and it'll therefore finish correctly.

that other guy
  • 116,971
  • 11
  • 170
  • 194
1

When you start your script, you start another job.
From the commandline you get the same behaviour after

(sleep 10 & while [[ $(jobs) ]]; do sleep 1; echo test; done)

When you source your shellscript, it stops after 10 (or in my test 5) iterations:

echo 'sleep 5 & while [[ $(jobs) ]]; do sleep 1; echo test; done' >job.sh
source job.sh
test
test
test
test
[1]+  Done                    sleep 5
test
Walter A
  • 19,067
  • 2
  • 23
  • 43