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:

  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.

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)