Ctrl+c vs kill-window
With Ctrl+c you normally (when stty -a
shows intr = ^C
) send SIGINT to processes in the foreground process group. You seem to assume any process that gets SIGINT exits (gets killed) but in general it may not.
You mentioned Ctrl+d, so probably there's an interactive shell between the tmux server and the process you want to terminate. In this case SIGINT is sent to the process, not to the shell; but even if the shell got it, it wouldn't exit. (I'm not saying it's always like this; I'm saying it matches your description).
So it seems after the process terminates upon Ctrl+c, you hit Ctrl+d to exit the shell.
When you kill a window in tmux, respective pseudo-terminals are closed and processes using them as their controlling terminals receive SIGHUP. Such a process may send SIGHUP to its children then (for a shell see this answer). A process may ignore the signal (see nohup
). Even if it ignores the signal, it may (or may not) die later if it tries to use a pseudo-terminal that is no more.
So in brief:
- you send SIGINT or SIGHUP to the process in question;
- it is up to the process if and how it reacts to SIGINT or SIGHUP.
Sending SIGINTs
A process may ignore SIGINT. If Ctrl+c does what you want for processes you're dealing with, then maybe it's enough to send SIGINTs to them prior to killing the window and sending SIGHUPs; as if you hit Ctrl+c in every pane.
The following command lists processes (immediate children of the tmux server) in panes in the current window, obtains respective foreground process groups and sends SIGINT to each:
tmux list-panes -F "#{pane_pid}" | xargs ps -o tpgid= | xargs -I{} kill -s SIGINT -{}
The problem is this very pipeline will also receive SIGINT and it may terminate prematurely. To deal with this
- run the command from elsewhere and use
-t
to specify a window,
- or implement some logic, so the foreground process group of the pipeline is excluded,
or run the pipeline in the background:
tmux list-panes -F "#{pane_pid}" | xargs ps -o tpgid= | xargs -I{} kill -s SIGINT -{} &
or make the command ignore the signal:
sh -c '
trap "" SIGINT
tmux list-panes -F "#{pane_pid}" | xargs ps -o tpgid= | xargs -I{} kill -s SIGINT -{}
'
I think the approach is prone to race condition though; things may change between ps
and kill
. A better(?) alternative is to send Ctrl+c to each pane:
tmux list-panes -F "#{pane_id}" | xargs -I {} tmux send-keys -t {} C-c &
Hook
Use this hook to make tmux send Ctrl+c to each pane in a window that is about to be kill-window
ed:
tmux set-hook before-kill-window 'run-shell "
tmux list-panes -t \"#{window_id}\" -F \"##{pane_id}\" | xargs -I {} tmux send-keys -t {} C-c
"'
This looks awful because there are three levels of quoting, still it seems to work in my tests (I haven't tested thoroughly though). The purpose of ##
is to defer expansion of #{pane_id}
. The point is #{window_id}
needs to be expanded when run-shell
works, but #{pane_id}
must survive and only be expanded when list-panes
generates its output.
Note the hook covers kill-window
but not kill-pane
, kill-session
or kill-server
. Also kill-window
is not just kill-pane
repeated several times, so before-kill-pane
hook will certainly not cover all cases when a pane is forcefully destroyed. And there are also respawn-pane -k
and respawn-window -k
commands.
What if it's not enough?
A process may not terminate upon SIGINT. With tmux list-panes -F "#{pane_pid}"
as a starting point you can try to obtain a list of PIDs (and/or PGIDs) you'd like to send SIGTERM (or even SIGKILL) to. The main problem with this general approach is it's possible to spawn a process and break its connection with its ancestors and tty completely.
Therefore in general you may not be able to track all processes that originated from the given tmux window.
pkill -s0
,pgrep -as0
,pkill -KILL -s0
(if a process did setsid(), it will survive it, as expected). I don't expect any terminal emulator to do that automatically, but you could probably create a key binding ;-) – Feb 21 '20 at 13:39