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)

tm.bashraw
#!/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.

Brett Terpstra

Brett is a writer and developer living in Minnesota, USA. You can follow him as ttscoff on Twitter, GitHub, and Mastodon. Keep up with this blog by subscribing in your favorite news reader.

This content is supported by readers like you.

Join the conversation