Search code examples
bashshellwhile-loopjobs

odd behavior of the "jobs" command in bash scripts


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.


Solution

  • 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.