How to make Ctrl+D detach tmux, while retaining GNU readline capabilities in Bash?
I'm not sure if I crafted the simplest possible solution. This:
stty eof '^T'
bind '"\C-d": "\C-x\C-t\C-x\C-d"'
bind -x '"\C-x\C-t": _tmux_detach'
_tmux_detach() { [ -z "$READLINE_LINE" ] && tmux detach-client; }
bind '"\C-x\C-d": delete-char'
Explanation:
-
stty eof '^T'
makes your terminal no longer send EOF at Ctrl+D. Now it's Ctrl+T. The new key combination will behave like the old one in readline, I mean it will exit the shell only if the line is empty. I deliberately chose Ctrl+T because it's very unlikely you will want to use its default binding (transpose characters) with an empty line.I thought that disabling EOF entirely (
stty eof undef
) would work, but it turned out readline(?) still reacts to the previously defined combination (like Ctrl+D) and makes the shell register EOF if the line is empty. bind '"\C-d": "\C-x\C-t\C-x\C-d"'
makes Ctrl+D "send" Ctrl+X, Ctrl+T and Ctrl+X, Ctrl+D. We will utilize the two sub-sequences separately.bind -x '"\C-x\C-t": _tmux_detach'
– From now on Ctrl+X, Ctrl+T executes_tmux_detach
…… which is a function that detaches
tmux
if the line ($READLINE_LINE
) is empty.bind '"\C-x\C-d": delete-char'
– From now on Ctrl+X, Ctrl+D deletes the character at point, like Ctrl+D usually does.
So Ctrl+D will work like this:
- if the line is not empty, the function is a no-op and
delete-char
does its job; - if the line is empty, the function does its job and
delete-char
is a no-op (it gets triggered but has nothing to do).
Notes:
-
READLINE_LINE
was introduced in Bash 4.0. - If you paste the code into your
~/.bashrc
, you may want to improve it: check if the shell is insidetmux
before changing the behavior of Ctrl+D, so shells outside oftmux
are unaffected. - It's important that
_tmux_detach
acts beforedelete-char
. If you reverse the order then you will introduce a bug: Ctrl+D that deletes a solitary character will also detachtmux
.
Kamil Maciorowski's answer shows it's possible, and I thank him. Per his mention of testing if one is in a tmux to determine the ctrl-d behavior, the following seems to work as desired, so I am expanding on his answer.
The tests within .bashrc:
- If not in a tmux session, don't remap keys
- If a tmux session exists, attach to it, retaining the remapped keys
- If not, create a new session employing the new keys
if [[ "$TERM" != "screen-256color" ]];then
if tmux has-session -t main 2>/dev/null; then
tmux attach -t main
else
tmux new -s main
fi
else
stty eof '^T'
bind '"\C-d": "\C-x\C-t\C-x\C-d"'
bind -x '"\C-x\C-t": _tmux_detach'
_tmux_detach() { [ -z "$READLINE_LINE" ] && tmux detach-client; }
bind '"\C-x\C-d": delete-char'
fi
Note the use of "screen-256color" (set by adding set -g default-terminal "screen-256color"
in .tmux.conf). If you don't wish to use the extended color map, you can simply declare "screen" instead in the .bashrc snippet.
The above may be more specific to my preferences than it needs to be - it is most certainly not the only way to do these tests, but it works for me. (updated to a smarter construct)