Josh's Toolbox

sa: My Wrapper Around GNU screen

 (Last edit: )
I spend almost all of my time in a terminal inside of a GNU screen session. Over the years, I’ve form fitted my usage of screen through a set of shell functions. Here, I talk a bit about why, how, and ultimately share my tool. I hope it inspires you to tailor-fit your favorite CLI tools.

I spend most of my (productive) digital time on the command line. I love its composability, automatability, and flexibility. My primary text editor is vim. I hate interacting with graphical git interfaces. And only rarely do I turn to graphical file managers for organizing my file system.

Early on in my technical journey, I compartmentalized my purpose-driven shell sessions through multiple windows or tabs. Eventually I reached a point where I wanted to liberate even that from the confines of the terminal software I was using. This would ensure a glitch or bug in the terminal emulator wouldn’t result in my current shell session being lost. And, I wouldn’t tie my flows to a given terminal, making them even more portable. So, I ventured into the world of terminal multiplexers (command line window managers of a sort).

The two most popular multiplexers are tmux and GNU’s screen, the latter taking second place to the programming language Go as the worst technology to try to search the Internet for. tmux was actually created to fix many of the deficiencies of screen. Yet, I still ended up on screen ostensibly for its ubiquity, but I was admittedly significantly motivated by my inability to figure out how to get rid of tmux’s status line.

Last year, there were some pretty big bugs found in screen, reported as CVEs (Common Vulnerabilities and Exposures) have revealed some weaknesses in the maintainability and the design of screen. Those particular bugs were fixed, but I want to be fully transparent about the state of screen. I continue to use it, but it is further motivation to switch to tmux and you should heavily consider it yourself. While this post will talk about screen, the concepts should be easily transferable to tmux, assuming it doesn’t already have better support for my use case than I thought.

While I was happy with my choice, there were a couple of problems that made interacting with instances of screen, or “screen sessions”, a bit of a pain:

GNU Screen version 4.09.01 (GNU) 20-Aug-23

Copyright (c) 2018-2023 Alexander Naumov, Amadeusz Slawinski
Copyright (c) 2015-2017 Juergen Weigert, Alexander Naumov, Amadeusz Slawinski
Copyright (c) 2010-2014 Juergen Weigert, Sadrul Habib Chowdhury
Copyright (c) 2008-2009 Juergen Weigert, Michael Schroeder, Micah Cowan,
Sadrul Habib Chowdhury
Copyright (c) 1993-2007 Juergen Weigert, Michael Schroeder
Copyright (c) 1987 Oliver Laumann

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program (see the file COPYING); if not, see

                  [Press Space for next page; Return to end.]

The copyright text `screen` presents on start-up.

To resolve these issues, I started calling into screen through a wrapper called ss, two short letters to quickly launch a session whenever I started a new terminal session. That name lasted about a day until I needed to call the very useful socket inspection tool ss. So, I switched the name to sa, which is luckily also so very slightly quicker to type.

The first iteration of ss sa was very simple:

sa() {
  screen -qR "$@"
}

# Fun fact: This would accomplish the same thing:
#     alias sa="screen -qR"
# And if both were set, the alias would win.

It simply called screen, telling it to not launch that full screen window (-q) and join the screen session specified after -R, creating one if none existed. The "$@" just passes any arguments I passed to sa on to the screen invocation so I could give sessions a name and pass any other arguments if I wanted to (I didn’t).

With this I had solved the full screen message problem and done a pretty good job of solving the per-shell initialization problem with a simple to type wrapper I could invoke on each launch. From there, it grew rather rapidly. I added -x to the invocation so I could attach to the same screen session in multiple shells, and added another -R so that I only had to pass part of an existing session’s name to rejoin it.

The real game changer was initializing it with human readable names without me passing anything. Most Linux distributions come with a list of words in various languages in the folder /usr/share/dict. My strategy sourced two random words from one of them and use that as the human readable name if one wasn’t provided. With over 4 billion possible combinations, I figured the chances of a collision were low enough to not care write code actively prevent them.

sa() {
  sessionName="$(grep -E '^[a-z]+$' /usr/share/dict/american-english \
    | shuf -n 2 \
    | tr '\n' '_' \
    | sed -Ee 's/_$//'
  )"
  screen -xqRR "${@:-sessionName}"
}

The above code uses bash’s parameter expansion ( ${<parameter>:-<default if not provided>}) to only pass that randomly generated sessionName to screen if arguments aren’t passed to sa. The sessionName calculation uses grep to grab only lower-cased words without extra symbols from the dictionary. That list is passed into shuf which selects two entries at random from it. Then tr replaces the new lines between the two selected words with an underscore. Finally, sed removes the trailing underscore because the output of shuf ends in a new line (which was turned into an underscore) and I didn’t want that in the final name.

And so you get fun and memorable names like:

And if you don’t like them, you can always pass your own, or update you current session’s name with Ctrl+A :sessionname <your session name>. This nonsense solved my human recollection problem.

And things have only grown from there. The latest version of the tool has so many other features, like:

All of this, and a bit more can be found over on sa’s tool page. If you run into any bugs, the man pages have but report contact information but don’t hesitate to get your hands dirty and shape it to your own needs.

That initial, simple screen wrapper created the entry point from which grew so much custom tooling to greatly streamline the way I interact with the command line and the utilities I invoke in it.

As I opened with, I love the flexibility of the Linux command line. Wrappers are a great example of that: your default interface into a tool need not be what the original developer intended. I encourage you to take a look at the tools you use on a daily basis to find ways you can make them that much faster. (I personally even wrap git itself.) Even if you don’t get back the time you spend customizing the tool on your future uses of it, I promise you the custom ergonomics are their own reward.

Till next time, cheers!

(And if you’ve been following my blog, I haven’t forgotten my promise to deliver my netcat wrappers.)

Update September 20, 2026: I noticed a grammatical error in the screen CVE notice that I fixed.