Linux – How to Make Ctrl+D Detach Tmux While Retaining GNU Readline in Bash

bashlinuxreadlinetmux

Based upon my days and days of researching this, I may be seeking the impossible.

Situation

  • I have my .bashrc nicely configured to either spawn a new tmux on connect, or if one exists, attach to it.

  • I always hit Ctrl+D to exit a shell session. It's burned into my muscle memory. Unlearning it after thirty years of systems administration is also asking the impossible.

  • I want to be able to detach from tmux using just Ctrl+D, rather than having it kill my shell.


My imperfect approach

I can bind Ctrl+D to detach in .tmux.conf.

The problem is that I also have emacs key-bindings burned into my muscle memory, so when I start editing a command line, I'll hit Ctrl+D to employ the GNU readline 'delete char under cursor'. Instead, the tmux binding swallows the Ctrl+D, so I'm immediately detached. Same thing if I'm editing in emacs.


Another flawed approach

GNU readline will take the EOF on an empty line, and then exit the shell. So I've tried trapping exit in .bashrc instead:

trap "~/tmuxexit" EXIT

with the contents of tmuxexit being:

tmux detach-client -s main

Which initially seemed like it worked, as hitting Ctrl+D on an empty line would report:

[detached (from session main)]

But tmux ls reports no server running on […].


Do I seek the impossible?

Best Answer

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:

  1. 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.

  2. 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.

  3. bind -x '"\C-x\C-t": _tmux_detach' – From now on Ctrl+X, Ctrl+T executes _tmux_detach

  4. … which is a function that detaches tmux if the line ($READLINE_LINE) is empty.

  5. 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 inside tmux before changing the behavior of Ctrl+D, so shells outside of tmux are unaffected.
  • It's important that _tmux_detach acts before delete-char. If you reverse the order then you will introduce a bug: Ctrl+D that deletes a solitary character will also detach tmux.
Related Question