Ok, I think I have a clue what is going on.
The behaviour you are observing in zsh and bash when executing echo $(mc)
is caused by mc
.
When you run mc
normally, it doesn't react when Ctrl+c is pressed, since it ignores SIGINT
. The way to end mc
is by pressing F10 and Enter.
When you run echo $(mc)
the input goes to the mc
process, so it is no wonder that when you press Ctrl+c nothing will happen, because mc
ignores SIGINT
.
But when you run echo $(mc)
and press F10 and Enter it will react.
(When I then press F10 and Enter again it actually quits; it should quit at the first try but I have no idea why it does not.)
From this fact I deduce that Midnight Commander is running normally, but the output from it is put into the shell buffer, so it can later be used by echo
.
Something we should consider normal.
Also you think that bash and zsh are hanging, but I would say that they are not hanging but waiting for the echo
command to return.
But echo
can only return when mc
returns, which it only does when F10 and Enter are pressed.
With this we can also explain what happens when you press Ctrl+z.
By pressing the combination mc
goes to sleep like it would if you had run mc
normally and echo
is still waiting for the sleeping mc
.
So everything is normal behavior.
Except for what fish does.
fish seems to start commands run with echo ($command)
in the background.
It kind of makes sense, since such a command normally does not need any input.
For the why and how I have no answer.
But you can see that it runs in the background when you enter
echo (mc)
jobs
After many, many hours of searching the face of the Internet, I have found the answer.
Linux has the notion of a process group.
The TTY driver has a notion of Foreground Process Group.
When you press Ctrl+C, the TTY sends SIGINT
to every process in the Foreground Process Group. (See also this blog entry.)
This is why both the compiled binary and the script that launches it both get clobbered. In fact I only want the main application to receive this signal, not the startup scripts.
The solution is now obvious: We need to put the application in a new process group, and make it the Foreground Process Group for this TTY. Apparently the command to do that is
setsid -c <applcation>
And that is all. Now when the user presses Ctrl+C, SIGINT will be sent to the application (and any children it may have) and nobody else. Which is what I wanted.
setsid
by itself puts the application in a new process group (indeed, an entire new "session", which is apparently a group of process groups).
Adding the -c
flag makes this new process group become the "foreground" process group for the current TTY. (I.e., it gets SIGINT
when you press Ctrl+C)
I've seen a lot of conflicting information about when Bash does or does not run processes in a new process group. (In particular, it appears to be different for "interactive" and "non-interactive" shells.) I've seen suggestions that you can maybe get this to work with clever pipe trickery... I don't know. But the approach above seems to work for me.
Best Answer
I don't think there's any way to turn it off.
First thought is to have preexec set a variable that indicates a command was run. If you pressed Ctrl+C at the prompt, it wouldn't get set.
But there's another similar problem: if you suspend a command, you'll get exit status 20 (zsh < 5.0.7) or 148 (bash and zsh >= 5.0.7).
To get around that, you can add
20|148
to thecase
statement above, i.e.If you're using zsh, make sure you have
setopt promptsubst
in your.zshrc
.If you're using bash, add:
but really you should use the more complicated DEBUG trap mentioned in https://superuser.com/a/175802/15334
Another possibility is that
setopt printexitvalue
is close enough to what you want.