James Ko James Ko - 1 month ago 6
Linux Question

Bash: How to get a script to rerun itself as a background task?

I'm writing a Bash script that is intended to be used as a daemon. If the user of my script does not pass a

--sync
option to the script, I want the script to rerun itself as a background task using that option. Here is my code (the last part was stolen from this SO post):

#!/usr/bin/env bash

args=("$@") # capture them here so we can use them if --sync's not passed
async=true

while [ $# -gt 0 ]
do
case "$1" in
--sync)
async=false
;;
# other options
esac
shift
done

# if --sync isn't passed, rerun the script as a background task
$async && exec nohup "${BASH_SOURCE[0]}" --sync "${args[@]}" 0<&- &> /dev/null &


For some reason, it doesn't seem to be working. When I do
bash -x myscript
(which helps debug the script), it seems that it just keeps on going even if
$async
is true, which I didn't think would happen since
exec
normally stops execution.

Likewise, if I run this command from my terminal:

exec nohup true 0<&- &> /dev/null &


it also fails to exit the shell, despite the use of
exec
. Why is this, and what can I do to work around it? (Bonus points: Is there any way to do this without creating a subshell?)

Thanks.

Answer

The & is being applied to the exec command itself, so exec foo & forks a new asynchronous subshell (or equivalent thereto, see below). That subshell immediately replaces itself with foo. If you want the parent (that is, your script) to terminate as well, you'll need to do so explicitly with an exit command.

The exec is probably not buying you anything here. Bash is clever enough to not actually start a subshell for a simple backgrounded command. So it should be sufficient to do:

if $async; then
  nohup "${BASH_SOURCE[0]}" --sync "${args[@]}" 0<&- &> /dev/null &
  exit 0
fi

I don't know of a way to do this without a subshell. But when you write shell scripts, you get subshells. Personally I'd just use a simple variable and test it with something like if [[ $async ]]; instead of executing true or false, but since those are also bash builtins, it's pretty well equivalent. In other shells they might run in subshells.

Now that I think of it, since you're reprocessing all the options in async execution anyway, you might as well just fork and exit from within the case statement, so you don't need the second check at all:

case "$1" in
    --sync)
        nohup "${BASH_SOURCE[0]}" --sync "${args[@]}" 0<&- &> /dev/null &
        exit 0
        ;;