In 2015, I wrote a blog post called some helpful tmux aliases explaining a bit about how I use tmux in my development workflow. Iāll confess itās not my most coherent blog post Iāve written, and Iāve iterated a bit since then, so I thought Iād take another run at it.
Wait what is tmux even all about?
When Iām working on a project, I usually need a bunch of separate shells.
Right now, as I work on this blog post, Iām using three:
- One to run my text editor, vim, where Iām writing these words
- Another to run my
exe/serve
helper script which runs the jekyll server, so I can preview the blog post in a browser and make sure it looks right
- Another for miscellaneous use, like running git operations to commit the changes or using ripgrep to search the project for references to things Iāve blogged about already
It would be fine and reasonable to just open three separate windows or tabs in my terminal emulator to run those various things, but instead I use tmux. I basically never have more than one actual tab or window, even when Iām bouncing between multiple projects, because my tmux workflow makes that unnecessary.
tmux is a āmultiplexerā, which is to say that it lets you run multiple shells in one shell.
It can split your screen vertically and horizontally to run as many shells as you want. In the screenshot at the top of this post, there are three shells. In tmuxās jargon, those are called āpanesā, like how a window may be made up of multiple panes of glass, this window is made up of multiple shells.
When I open too many panes, it can start to feel a little cramped. I often resize them by dragging the little border line. I also will often maximize the current pane, hiding the others, if I just need to focus on one task for a bit:
You can also run multiple windows, and switch between those. This is nice because sometimes you want something running in the background and you donāt really want to look at it all the time. These are called āwindowsā but I tend to think of them as ātabsā, because they function sort of like tabs in a GUI app. You can even rename windows to help you remember what that windowās purpose is:
The last major bit of tmux terminology, after āpaneā and āwindowā, is āsessionā. A session is a group of windows. You can have many sessions running at once, and switch between them. I have one session per project that Iām working on. If I initiate a session in the directory for my project seasoning, then every new pane and window will start a new shell in that directory, which is really convenient.
So thatās the overview.
Aliases
Creating a new session
You can start a new session by simply running tmux
. If you do that, your session will automatically be given a name of 0
. Your next session will be called 1
, and so on. Because I run one session per project, and sometimes work on multiple projects at the same time, I like to name my sessions after the project. Thatās easy enough to do. Instead of running simply tmux
to start a session, I can run, for example:
tmux new-session -s seasoning
That will start a new session called seasoning
in the current directory.
Back in 2015, I came up with a clever alias that would automatically infer the session name from the name of the current directory. It looked like this:
alias 't'='tmux new-session -A -s $(basename $PWD | tr -d .)'
Running basename $PWD | tr -d .
when youāre in a directory like /Users/max/src/gh/maxjacobson/seasoning
prints the text seasoning
, which seems like a perfect session name for when Iām working on that project.
The -A
bit will attach to an existing session with that name, if one exists, and otherwise create it.
With this alias, I can happily run simply t
in any directory and feel confident that Iāll be in a nicely-named session.
This held up pretty well over the years, but it has one flaw: every now and then (and this is pretty rare) Iāll have more than one āprojectā with the same name. For example, Iāll often clone other peopleās dotfiles repos and rummage around for inspiration. Theyāre almost always called dotfiles
. If I clone wfleming/dotfiles and start a new session by running t
, Iāll get a new session called dotfiles
in Willās dotfiles. If I then clone pbrisbin/dotfiles and run t
, tmux will see that there is already a running session called dotfiles
and attach to that instead of creating a new one.
This has only come up a very small handful of times but every time is a little papercut that has bugged me. So, recently, I revised my t
alias for the first time in ages. It now looks like this:
alias 't'='tmux new-session -A -s "$(basename $PWD) $(echo $PWD | shasum -a 256 | cut -c1-4)"'
With this new version, the derived session name when Iām in /Users/max/src/gh/maxjacobson/seasoning
is seasoning 3f2c
. That bit at the end generates a unique four character hash based on the absolute path to the project. It will always come up with the same hash, so it will be possible to run t
in as many dotfiles repos as I want and start up independent sessions.
Listing sessions
You can run tmux list-sessions
to print out a list of the running sessions, plus some interesting metadata about them:
$ tmux list-sessions
hardscrabble_github_io afd6: 4 windows (created Sat Oct 21 15:06:31 2023) (attached)
seasoning 3f2c: 1 windows (created Sat Oct 21 15:06:24 2023)
Iāve had that aliased to tl
for years. Today I learned you can format the output to include whatever information you want, and spent several minutes exploring various ideas.
Some ideasā¦
If I want to correct the pluralization error of ā1 windowsā:
$ tmux list-sessions -F '#{session_name} (#{session_windows} #{?#{==:#{session_windows},1},window,windows})'
hardscrabble_github_io afd6 (4 windows)
seasoning 3f2c (1 window)
That one is uh, pretty gnarly. In English, itās saying āIf the session_windows
variable is equal to 1, say āwindowā otherwise say āwindowsāā.
If I want to include the sessionās directory:
$ tmux list-sessions -F '#{session_name} (#{session_path})'
hardscrabble_github_io afd6 (/Users/max/src/gh/hardscrabble/hardscrabble.github.io)
seasoning 3f2c (/Users/max/src/gh/maxjacobson/seasoning)
$ tmux list-sessions -F '#{session_name} (#{d:session_path})'
hardscrabble_github_io afd6 (/Users/max/src/gh/hardscrabble)
seasoning 3f2c (/Users/max/src/gh/maxjacobson)
$ tmux list-sessions -F '#{session_name} (#{b:session_path})'
hardscrabble_github_io afd6 (hardscrabble.github.io)
seasoning 3f2c (seasoning)
As you can see, there are modifiers to just say the parent directory, or just say the basename of the directory.
If I want to scrub out the unsightly trailing hash from the session name:
$ tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}'
hardscrabble_github_io
seasoning
I feel like I should be able to simplify that regular expression to something like / [a-f0-9]{4}$/
but I canāt quite figure out how to escape it. So it goes.
I think Iām going to keep it minimalist and use this last one when I run tl
. That means my next recommended alias looks like this:
alias 'tl'="tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}' 2>/dev/null || echo 'no sessions'"
Youāll notice that thereās a little bit of error handling in there. Thatās because, by default, tmux list-sessions
throws a kind of unsightly error when there are no sessions to list. We can make that a little nicer.
Attaching to an existing session
One of the benefits of tmux is that if you accidentally close your terminal emulator app (e.g. Terminal or iTerm 2 or Alacritty or whatever), your session is still running in the background, and you can reattach to it. Itāll keep running until all of the shells within the session exit. I normally press Ctrl + d to exit shells, but you can also type exit
and hit enter.
You can attach to a session by running tmux attach-session
, which will attach to the most recently used session. I have this aliased to ta
like so:
alias 'ta'='tmux attach-session'
This is usually what I want. From there, if I happen to have multiple sessions going, I might switch to another session like so:
(Eagle-eyed readers might notice that the hash has disappeared from the status bar in the lower right in that gif, because I realized I can use the same format string trick to scrub it from there too. And now you know that I am a bit too lazy to redo the earlier gifs for consistencyās sake.)
Itās also possible to attach to a specific session, rather than the most recently used one. You can do that by running a command like this:
$ tmux attach-session -t seas
Thankfully you donāt need to specify the full name. tmux can figure out that when I specify seas
I mean seasoning 3f2c
.
I have that aliased as to
, so I can simply run:
That alias looks like this:
alias 'to'='tmux attach-session -t'
Wrapping it all up
Alright, thanks for coming on this journey. Hereās the aliases all together:
alias 't'='tmux new-session -A -s "$(basename $PWD) $(echo $PWD | shasum -a 256 | cut -c1-4)"'
alias 'tl'="tmux list-sessions -F '#{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}' 2>/dev/null || echo 'no sessions'"
alias 'ta'='tmux attach-session'
alias 'to'='tmux attach-session -t'
And hereās the relevant bits of config in my ~/.tmux.conf
set -g status-interval 1
set -g status-left ""
set -g status-right "%b %d %H:%M:%S #{s/ [a-f0-9][a-f0-9][a-f0-9][a-f0-9]$//:session_name}"
Happy tmuxing. See you in another eight years.