Yeah, and all those concurrency controls of the shell suck. Here's a snippet to run N commands in parallel, wait for all of them, while supporting interrupting them with simple Ctrl+C:
(
trap 'kill 0' SIGINT;
for i in {1..$N}
do
whatever_command $i &
done
wait
)
The parentheses are absolutely crucial there for some reason, the { } won't give you pass-through of SIGINT. No wonder people keep re-inventing PID files, manually tracking $! and all kinds of other crazy bullshit that work 90% of the time, but sometimes fail to properly terminate the runaway children.
> The parentheses are absolutely crucial there for some reason, the { } won't give you pass-through of SIGINT.
This is a gross misunderstanding of what is going on (signals are never "passed through", the process is either in the same group or it isn't), but the real issue here is the confusing semantics of bash's job controls. I personally would have swapped the semantics of { command blocks } and ( subshells ) too, but bash is pushing 40 so maybe it made more sense then.
It will sorta behave properly if you disable job control or put it in a script:
it might also kill your current shell too though. This really seems to boil down to not understanding the difference between { } and ( ) in bash.