Making my tmux life easier
Have you heard of tmux
? Do you ache for terminal mutiplexing and persistent shell sessions? If not, you can probably skip this post. Hang tight, I have something more generally palatable in the works. In the meantime, if you want to read up, head to the tmux homepage.
I’ve been enjoying tmux
for a while now. I was never a big screens
user, but I’d played with it as a basic replacement for nohup
on remote sessions. tmux
just clicked for me when I sat down to figure it out, though, and I’ve been using it in various ways since.
I’m aware that there’s a method to the madness behind the varying flags used when calling “attach” vs. “new,” for example, but I always get my -s
, -t
, and -n
switched up. I wrote a wrapper to help me out.
Because it only wraps the “attach” and “new” commands, this would generate nested sessions if run within tmux
. It’s only really handy if you detach from your sessions regularly instead of just flying between multiple sessions/windows like a pro. I use tmux
primarily for setting up remote connections and running on remote servers, so I drop back to Terminal frequently and jump back to sessions as needed.
When sourced in your .bash_profile
or .zshrc
, it gives you a tm
command that you can run with one or two arguments. The first argument is always a session name, the second can specify a window. If the session name you provide matches an existing session (partial names work), you’ll be connected to it, and optionally to a specific window in the session if you supply that argument.
The window name can be partial, too. So if I have a session named “jekyll” and a window called “dev,” I can connect to that window just by running tm jek dev
, or just tm j
if there are no other sessions starting with “j” and “dev” was my last-used window. If multiple sessions or windows match your partial, the first one alphabetically will be selected.
If no matches are found for the session name, a new session will be created and attached. The second argument can be used to name the first window upon connection.
Here’s the script, do with it what you will. (Updated 2019-12-17)
#!/bin/bash
__lower() {
echo "$@"|tr "[:upper:]" "[:lower:]"
}
__menu() {
local result=""
PS3=$1
shift
select opt in "$@" "Cancel"; do
if [[ $REPLY =~ ^[0-9]+$ ]] && [[ $REPLY -le ${#options[@]} ]]; then
result=$opt
break
else
result="CANCEL"
break
fi
done
echo $result
}
# tmux wrapper
# tm session-name [window-name]
# Names can be partial from the beginning and first match will connect.
# If no match is found a new session will be created.
# If there's a second argument, it will be used to attach directly to a
# window in the session, or to name the first window in a new session.
#
# Update 2019-12-12
# Case insensitive session/window matching
# Allow numeric index for window (by index)
# Create new named window if second argument passed but no existing match
# Replace a bunch of sed and awk crud with -F params
# Add select menus when running without arguments
__tm() {
local tmux_cmd
tmux_cmd=${TM_TMUX_COMMAND:-"tmux"}
local attach window cleft cright
if [[ -n $TMUX ]]; then
echo "Already in a tmux session"
return 1
fi
if [ -n "$1" ]; then
attach=""
tmux has-session -t $1 > /dev/null
if [ $? -eq 0 ]; then
attach=$1
shift
else
for session in `tmux ls -F '#S'`;do
cleft=$(__lower $session)
cright=$(__lower $1)
if [[ $cleft =~ ^$cright ]]; then
echo "Matched session: $session"
attach=$session
shift
break
fi
done
fi
if [[ $attach != "" ]]; then
if [ $# -eq 1 ]; then
if [[ "$1" =~ [0-9]+ ]]; then
window=$1
else
for win in `tmux list-windows -F '#W' -t $attach`;do
cleft=$(__lower $win)
cright=$(__lower $1)
if [[ $cleft =~ ^$cright ]]; then
echo "Matched window: $window"
window=$win
break
fi
done
fi
if [[ -n $window ]]; then
$tmux_cmd attach -t $attach:$window
else
$tmux_cmd new-window -t $attach -n $1
$tmux_cmd attach -t $attach:$1
fi
else
$tmux_cmd attach -t $attach
fi
else
if [ $# -gt 1 ]; then
attach=$1
shift
$tmux_cmd new -s $attach -n $1
else
echo "Attempting to create $1"
$tmux_cmd new -s $1
fi
fi
else
$tmux_cmd ls &>/dev/null
if [[ $? -gt 0 ]]; then
$tmux_cmd new
return 0
fi
if $(which fzf &>/dev/null); then
attach=$(tmux ls -F '#S'|fzf +m --cycle -1 --height=8 --layout=reverse-list --prompt="Choose session> ")
if [[ -n $attach ]]; then
declare -a windows=( $(tmux list-windows -t $attach -F '#W') )
if [[ ${#windows} > 1 ]]; then
window=$(printf '%s\n' ${windows[@]}|fzf +m --cycle -1 --height=8 --layout=reverse-list --prompt="Choose window> ")
$tmux_cmd attach -t $attach:$window
else
$tmux_cmd attach -t $attach
fi
fi
else
options=($($tmux_cmd ls -F '#S'))
if [[ ${#options[@]} -gt 1 ]]; then
attach=$(__menu "Select session: " "${options[@]}")
if [[ "$attach" != "CANCEL" ]]; then
options=($($tmux_cmd list-windows -t $attach -F '#W'))
if [[ ${#options[@]} -gt 1 ]]; then
window=$(__menu "Select window: " "${options[@]}")
if [[ $window == "CANCEL" ]]; then
return 0
fi
else
window=${options[0]}
fi
$tmux_cmd attach -t $attach:$window
fi
else
$tmux_cmd attach -t ${options[0]}
fi
fi
fi
}
__tm $@
Just for fun
Here’s a quick snippet you can use in your bash_profile to display a “session:window” banner when you connect to a new window. It uses Figlet, but you can do whatever you want with the $sessionname
and $winname
variables.
# print a banner for the current window name within tmux
tm-win-banner() {
local winname sessionname
if [[ -n $TMUX ]]; then
sessionname=`tmux list-sessions | grep attached | awk -F: '{print $1}'`
winname=`tmux list-windows | grep --color=never -E '^\d+: (.*)\*'| awk '{ print $2 }' | tr -d '*'`
figlet -w `tput cols` -f graffiti "$sessionname:$winname"
fi
}
When run, this gives me:
You can also split the session and window names up and use them in a PROMPT_COMMAND, but if you’re in tmux you already have a status bar. If you use iTerm 2’s tmux integration, all tabs and windows share the same session, so it’s ultimately kind of pointless. Whatever.
tm-session() {
local sessionname
if [[ -n $TMUX ]]; then
sessionname=`tmux list-sessions | grep attached | awk -F: '{print $1}'`
echo -n $sessionname
fi
}
tm-window() {
local winname
if [[ -n $TMUX ]]; then
winname=`tmux list-windows | grep --color=never -E '^\d+: (.*)\*'| awk '{ print $2 }' | tr -d '*'`
echo -n $winname
fi
}
Ryan Irelan has produced a series of shell trick videos based on BrettTerpstra.com posts. Readers can get 10% off using the coupon code TERPSTRA
.