Am 16.01.23 um 18:26 schrieb Chet Ramey:
The fix is to add enough state machinery to detect this situation and behave in a way that can satisfy both the standard and the later interpretation, while being careful not to undo this work later. This is obviously not how bash worked in the past.
Thanks for the explanation. While editing the state machinery I would like to suggest to add a new shopt, let's call it keepsigint, which a user may set to preserve the SIGINT trap set in the parent shell for all asynchronous commands. While the POSIX behavior to ignore SIGINT for background processes if job control is disabled makes totally sense for interactive shells, for scripts to me it often appears not constructive. Please consider a script launching several commands in background and waiting for their completion: cmd1 & cmd2 & wait If the user having launched this script from the interactive terminal aborts it by hitting Ctrl+C, by default, the shell sends SIGINT to the process group (pgid) of the script. However, while cmd1 and cmd2 get their signal, they usually (if they don't override it) ignore it due to above POSIX requirement. In my experience, what the user usually wants in such a case is to abort cmd1, cmd2 as well as the script having launched them. Of course there are ways to kill cmd1 and cmd2 (and possible grandchildren) explicitly, e.g. by sending an additional TERM signal to the process group, e.g. (at the top of the script) trap 'trap "" TERM; env kill -TERM -- -$$; exit 130' INT However, this is usually only safe, when we are the process group leader (otherwise we might kill our parent as well!), so we need an additional [ $$ -eq $(($(ps -o pgid= -p "$$"))) ] || exec setsid --wait "${BASH_SOURCE[0]}" "$@" at the top of our script to create a new process group if necessary. Further, applications may react differently on TERM and INT, making the "signal conversion" undesirable in the general case. Finally, asynchronously running bash scripts may print "Terminated" messages which are usually not of interest for a user having aborted the command manually. Another option would be to enable jobcontrol within the script and kill the commands that way, e.g. set -m; cmd1 & jobs=($(jobs -p)); env kill -INT -- "${jobs[@]/#/-}" However, jobcontrol disables the possibility to suspend the "whole script" with Ctrl+Z and bears the risk to eventually loose some jobs while without jobcontrol, killing the single pgid kills all leftovers with high certainty. A third way is to launch cmd1 and cmd2 with env --default-signal=SIGINT,SIGQUIT cmd1 & so they do not ignore SIGINT. That's fine, but has to be repeated for every command. Further, process substitutions and functions cannot be called that way. A fourth way is to explicitly set the INT trap within an async command group before executing the command, like { trap 'true' INT; exec cmd1; } & Personally I regularly use below __async__ function for async commands, command groups, process substitutions and functions and I'm fine with that. But all these four options require some typing (and reading) overhead and just don't feel "sane". I think, bash would really benefit from a 'keepsigint' option. What are your thoughts about that? Thanks and kind regards Tycho _____________________________________________________________________________ __async__ bash -c 'echo first; trap -p; sleep 6'; wait { __async__; exec bash -c 'echo second; trap -p; sleep 6'; } & wait foofunc(){ bash -c 'echo foofunc; trap -p; sleep 6'; }; __async__ foofunc; wait cat <(__async__; exec bash -c 'echo psub; trap -p; sleep 6';) __async__(){ local int_trap int_trap="$(trap -p INT)" [ -z "$int_trap" ] && int_trap="trap -- 'exit 130' SIGINT" if [ "${#@}" -eq 0 ]; then # Already running async, just set parent's INT handler. eval "$int_trap"; return fi if [[ $(type -t "$1") == file ]]; then # exec into external file so pid is same as if called like 'cmd &' { eval "$int_trap"; exec "$@"; } & else { eval "$int_trap"; "$@"; } & fi }