Search code examples
bashcommand-substitutionsetsid

Why is this command substitution waiting for the background job to finish?


I'm trying to get the PID of a background job with command substitution. The background job is started with setsid. The problem is that the parent process is struck at the command substitution.

Here is an example script:

#!/bin/bash

if [ "$1" = "start" ]; then
    while true; do date > "bg.date"; sleep 1; done &
    echo $!
    exit 0
fi

pid="$(setsid "$0" start)"
echo "pid=$pid"
  • ./script start works as expected (ie. exits immediately to bash prompt while background job is running).
  • setsid ./script start also works as expected.
  • But ./script does not work as expected: it does not print the PID (unless the background job is killed manually).

Solution

  • Command substitution is implemented via pipes. The read end waits for all writers to close, but you're keeping the write end open indefinitely as the stdout of your infinite loop.

    You can redirect the loop to avoid this:

    #!/bin/bash
    
    if [ "$1" = "start" ]; then
        while true; do date > "bg.date"; sleep 1; done > /dev/null &
        echo $!
        exit 0
    fi
    
    pid="$(setsid "$0" start)"
    echo "pid=$pid"
    

    If the re-entrant setsid was part of an attempt to make the script not hang, you'll be pleased to know that this is unnecessary and you can rewrite it more robustly with a simple function.