2

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?

2

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.
| improve this answer | |
  • 1
    You sir, are a steely-eyed missile man. This works perfectly. The impossible is possible. I'm indebted! – anastrophe Aug 2 '19 at 6:02
1

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)

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.