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:

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: