Tmux Tips

Attach or create automatically

Attach to an existing tmux session if there is already one. Otherwise, creating one.

function tg {                   # tmux go!

    # Attach to a session. If it does not exist, create that session. If no
    # session name specified, prompt to choose one from existing ones.

    # A simple/naive replacement of this "bloated" function:
    # tmux -2 attach -t "$session_name" || tmux -2 new -s "$session_name"

    local usage="tg [-d] [session_name]"
    local detach_others=""

    while getopts "d" opt; do
        case $opt in
            d) detach_others="-d";;
            ?) echo $usage >&2;;
        esac
    done
    shift $((OPTIND - 1))

    local session_name="$1"

    if [ -n "$session_name" ]; then
        tmux -2 attach $detach_others -t "$session_name" \
            || tmux -2 new -s "$session_name"
        return
    fi

    # No session name specified, act according to the number of sessions
    local sessions=$(tmux list-sessions -F "#{session_name}")

    if [ -z "$sessions" ]; then
        # No session, create a default one
        tmux -2 new -s 'misc'
        return
    fi

    if [ $(echo "$sessions" | wc -l) -eq 1 ]; then
        # Only one session existing, attach to it without prompt
        tmux -2 attach $detach_others -t "$sessions"
        return
    fi

    # Multiple sessions, prompt to choose one
    local IFS=$'\n' # In case session names contain whitespaces. Must 'local' to
                    # NOT pollute the global 'IFS'. $'LITERAL_STR' => ansi-c
                    # quoting
    local PS3="Select a session: "

    select session_name in $sessions; do

        if [ -n "$session_name" ]; then # A valid choice
            tmux -2 attach $detach_others -t "$session_name"
            return
        else
            echo "Invalid index '$REPLY', please retry"
        fi

    done
}

Temporarily "maximize" a pane

Tmux 1.8 or above have built-in support ('Prefix + z')

This is for older tmux from http://superuser.com/questions/238702/maximizing-a-pane-in-tmux

unbind +
bind-key + run "if [[ $(tmux list-window) =~ MAX ]]; then \
         tmux last-window;\
         tmux swap-pane -s MAX.0; \
         tmux kill-window -t MAX; \
         else tmux new-window -d -n MAX; \
         tmux swap-pane -s MAX.0; \
         tmux select-window -t MAX;fi"

Automatically attach to a session on login

There are a handful of ways to achieve this, each of which affects different set of clients. Choose per your need/trade-off.

  • To do it once
    ssh user@host -t 'tmux attach -t foo'
    
  • For PuTTY users,
    1. Put "tmux -2 attach || tmux -2 new -s foo" into PuTTY's configuration panel: "connection -> ssh -> remote command".
    2. In "connection -> data", add environment variable LANG=en_US.UTF-8.
  • If you are using "konsole" (locally), edit konsole's profile "general->command" to this:
    /bin/bash -c "tmux attach || tmux new -s foo"
    
  • Use the feature "forced command" of SSH.

    This affects ssh connections only. For details, refer to my ssh tips.

    Note that:

    • Specify absolute path for tmux.
    • Set 'LANG' to utf8 so that pane borders are shown correctly.
  • The most aggressive way, attach to tmux in .bash_profile

    This measure takes effect wherever you connect from, whatever protocol you are using.

    Put this following line into your .bash_profile

    [ -n "$TMUX" ] || tg
    

    Note that:

    1. "tg" is the function alias defined previously
    2. This line needn't be the last line in your profile
    3. NOTE: Connecting to localhost results in recursive tmux sessions, which eventually render the tmux session unusable. ssh localhost -t /bin/bash if you really need to login back to localhost since this way a non-login interactive shell is opened.

Resize current pane without mouse

resize-pane -[UDLR] N

where U for Up, D for Down etc. and N is adjustment in cells or lines.

Or, F12 M-<arrow>.

Share "clipboard" between Emacs and Tmux

(defun lgfang-send-tmux-copied ()
  "Send head of the kill-ring to tmux's buffer"
  (interactive)
  (let ((file (make-temp-file "/tmp/emacs-to-tmux.clip")))
    (with-temp-file file (insert-for-yank (current-kill 0)))
    (call-process "tmux" nil nil nil "load-buffer" file)
    (delete-file file)))

(defun lgfang-get-tmux-copied ()
  "Get current tmux buffer"
  (interactive)
  (call-process "tmux" nil t nil "show-buffer"))

Populate the "clipboard"

function copy_to_tmux {
    # usage: cat file | this_function
    while read line; do
        tmux set-buffer "$line"
    done
}

Share windows among sessions

In addition to move windows among sessions, it is also possible to make certain windows present in multiple sessions simultaneously.

tmux link-window -s session:window
tmux unlink-window

Share sessions with another account

It seems to me the only benefit is that you do not share your password.

  • To share all:
    1. Find corresponding socket by echo ${TMUX%%,*} in the session
    2. Make it readable/writable to others chmod 777 /path/to/socket
    3. Others attach with tmux -S /path/to/socket attach
  • To share only one session: start that session with a separate socket, make it readable/writable by others.
    tmux -S /path/to/socket_to_share [new-session -s session_name]
    

NOTE: according to test:

  • If the socket is writable by others, you (the tmux owner) cannot connect to the tmux server unless explicitly "-S".
    $ tmux list-sessions
    health: 2 windows (created Thu May 29 09:30:17 2014) [167x44]
    package: 3 windows (created Wed May 28 09:02:17 2014) [167x44]
    
    $ chmod -R 0770 /tmp/tmux-30435/
    
    $ tmux list-sessions
    can't create socket
    
    $ tmux attach
    can't create socket
    
    $ tmux -S /tmp/tmux-30435/default list-sessions
    health: 2 windows (created Thu May 29 09:30:17 2014) [167x44]
    package: 3 windows (created Wed May 28 09:02:17 2014) [167x44]
    
  • Once your peer connected to your tmux, you can chmod back without breaking his/her connection.

Rescue

Socket file deleted by mistake?

According to tmux manul

If the socket is accidentally removed, the SIGUSR1 signal may be sent to the tmux server process to recreate it.

Where is my processes?

Once for a while, I want to "gracefully" kill a process spotted by "ps". The following command lists ttys of every pane, therefore helps to find out in which pane a process is in

function tt {
    tmux list-panes -a -F '#S:#I.#P #{pane_tty}'
}
export -f tt

-s [-t session_name] instead of -a for panes in a session only

Backspace and del not working?

The root cause

+-------------------------------------------------+
|   +-----------------------------------------+   |
|   |                                         |   |
|   |                                         |   |
|   |                                         |   |
|   |                                         |   |
|   |                   erase = ^H            |   |
|   |                                         |   |
|   |                                         |   |
|   |                                         |   |
|   | inner shell                             |   |
|   +-----------------------------------------+   |
|                 input_erase = ^H                |
|             forwarded_erase == ^?               |
| tmux                                            |
+-------------------------------------------------+

It is very likely an issue caused by the mismatch of the setup of the <erase> character. Upon receiving an <erase>, tmux always forwards one "^?" (control + ?) char to the inner shell. But, the inner shell does not necessarily interpret "^?" as <erase>.

For example, a typical cause of the issue is: one set <erase> to "^h" in his/her profile. This causes problem because:

  1. Tmux gets a "^h" and recognize it as an <erase> because that is how it was configured in the profile.
  2. Tmux sends out "^?" to the inner shell.
  3. The inner shell gets a "^?" but does NOT interpret it as an <erase>.

Solutions

  • A workaround: run stty erase ^? in each problematic inner shell.

    This is not recommended, but it does provide a remedy when you cannot afford restarting the tmux server.

  • The once-for-all solution: put stty erase ^? into your profile.

    This way, you use "^?" for <erase> everywhere consistently. In fact, most "modern" software interpret "^?" as <erase> by default. Therefore, this solution is not only a once-for-all solution but also brings you least surprise.

    1. Put stty erase ^? into your profile and apply it.
    2. Restart tmux server (NOT re-attach!)
    3. Configure your terminal emulator (say putty) to send "^?" when the <backspace> key is pressed.
  • Another workaround: leave profile unchanged but stty erase ^? before starting tmux server. (You run "stty" only once and attach to tmux sessions as usual later on.)

    This works since tmux sees "^H" as a normal char and forwards it without conversion and then the inner shell regards the "^H" as an erase.

    This workaround is useful if you cannot set erase to "^?" permanently (say you are sharing the lab with others and all others are accustomed to "^h").

Environment

Note that there are global environment (inherited when a tmux server starts) and session specific environment. And, these environments persist so long as the server/session are alive.

Therefore, you can set environment variables (via set-environment) and impact all new panes.

One use case of this technique is: I share an account with others. I want to use a really fancy PS1, which others might dislike. Hence, instead of modifying ~/.bashrc, I run the following command to make current fancy PS1 a session-wise environment and impacts all new shells in and only in my session.

tmux set-environment -t my_session PS1 "$PS1"

My configure

My configure file for tmux can be found here: https://github.com/lgfang/dotfiles/blob/master/.tmux.conf

This file 'source' color themes defined in files under ~/.tmux. Therefore, please remember to copy my color theme files as well: https://github.com/lgfang/dotfiles/tree/master/.tmux

An example of scripting tmux

#!/bin/bash

# Created:  Fang lungang 2011
# Modified: Fang Lungang 10/04/2014 15:07>

set -o nounset
set -o errexit
set -o pipefail
# set -x

REMOTE_HOST="135.1.2.3"
REMOTE_LOGIN="lgfang"
MY_PATH=".local/bin/tmux-dwim" # path to this script on the remote host
REMOTE_CMD="source .bash_profile; $MY_PATH"
SESSION_NAME="hi"

# Attach if the session already exists
if tmux has-session -t $SESSION_NAME 2>/dev/null; then
    exec tmux attach -t $SESSION_NAME
fi

# Start a session
tmux new-session -d -s $SESSION_NAME

if hostname | grep -q "\(lgfang\|lungang\|PA0019712\)"; then
    this_is_mypc=true
else
    this_is_mypc=false
fi

# linux ping and windows ping use different options for the same meaning
if which ping | grep -q -i windows; then
    PING="ping -n 1 -w 1"
else
    PING="ping -c 1 -W 1"
fi

if ! $this_is_mypc; then
    # I am already on the "remote_host"

    # Start Emacs in a window with specified number
    tmux new-window -d -n emacsclient -t $SESSION_NAME:9 \
        "emacsclient -t || (emacs --daemon && emacsclient -t)"

elif $PING $REMOTE_HOST; then
    # I am on the local PC and remote host ping-able.

    # Run this script on the remote host to create a similar session
    tmux new-window -d -n $REMOTE_HOST -t $SESSION_NAME:9 \
        "ssh -o BatchMode=yes -t $REMOTE_LOGIN@$REMOTE_HOST '$REMOTE_CMD'"

    tmux select-window -t $SESSION_NAME:9
    # make it feels like I'm working on the remote host locally
    tmux set-option -g -t $SESSION_NAME status off
    tmux set-option -g -t $SESSION_NAME prefix F11
fi

exec tmux attach -t $SESSION_NAME

Created: 2015-11-27 Fri 09:46 by Emacs 24.5.1 (Org mode 8.2.10)

comments powered by Disqus