As a programmer, my computer use can sometimes look anachronistic,
what with my heavy use of Emacs and my use of the terminal to get my
work done. I spend an
inordinate amount of time looking at monospaced text, and I get a lot
of my work done inside my shell of choice,
zsh(1)
, although admittedly, most of my
experience with the shell has been in
bash(1)
, and so I’m more
familiar with a lot of bash
-isms, especially when it comes to the
affordances the shell gives you for writing shell scripts — a
lot of things are transferable between the two, nevertheless, so I
found it quite nice to switch, and there’s a bunch of things that make
zsh(1)
a slightly more pleasant environment to use interactively.
I really do think that knowing how to use the shell is a Must Have skill for any developer, even if you don’t spend your time in the terminal like I do. It doesn’t mean you have to write shell scripts per se; it can be as simple as figuring out how to get the most out of your shell of choice, knowing how to construct pipelines, or exploiting various features of modern-day shells. Here’s a few of the stuff that make my life on the command line pleasant and productive.
Creature Comforts
My use of the command line can be anachronistic, but that doesn’t mean I have to work like it’s the 1970s through a slow teletype and a 2400 baud modem. No, this is 2018 dammit, and that means using a bunch of features that your terminal emulator and shell gives you, including color, line editing, history, and tab completion— things that you need today to get the most out of your shell experience.
But! The first deal is a choice of typeface and a choice of screen colors. You’re going to stare at that screen for hours; better choose a typeface that’s both readable and looks good. My current dev machine is does not have a Retina screen, so that also means the typeface has to have good contrast and letter shapes at low pixel densities.
For a while, I used one of the preinstalled fixed-width typefaces on macOS (née OS X), Monaco, which was nice, but had some issues at the small sizes I kept using it in. I experimented with turning off antialiasing at the size I used (10pt), which yielded fairly nice sharp text, but I felt wrong turning off antialiasing at all, even though at the pixel density of my screen it did help a lot with readability. It just felt ugly to me, which is definitely not an objective assessment to be fair: I just wanted it to look good as well.
I tried out various other typefaces, including Adobe’s Source Code Pro and Inconsolata, but I finally settled on Hack, for mostly subjective reasons. I like how Hack’s glyphs look narrower to me than the others, and I also liked how code looks in it. Inconsolata looked too muddy at the size I wanted, while Source Code Pro’s glyphs looked too wide and squat for my taste. Another issue was that I wanted the same typeface on both Emacs and iTerm2; Emacs had a weird time handling Source Code Pro at the size I used. So, Hack it is.
In terms of colors, I like to use a dark background, instead of
blasting my eyes with all that light, especially at night. Admittedly,
my choice of colors is terrible in bright sunlight: I sometimes have
difficulty reading my screen under the glare of sunlight, but that’s a
situation I’m rarely in, so it’s worth the tradeoff. I have no idea
where my current colors come from, to be honest, as I’ve tweaked it
over the years. I think it was originally a preset with white text on
a dark blue background, which then became a dark grey background,
similar to Slate or what not, and which then turned back into a darker
blue background. I probably adopted the color scheme from my
xterm(1)
and Gnome Terminal presets way back. If you want to check
it out, you can download it
here.
Aside from the eye candy (which, if I’m honest, it is), I also prefer
to keep a terminal window invokable via a hotkey, a habit I picked up
years ago on Linux when I used ion3
as my window manager of
choice. That terminal serves as my go-to place to Get Things Done, if
it’s something that isn’t particularly related to a project. I like to
keep the terminal output consistent, so that only things related to a
project (such as build results, or a log tail) are in a particular
terminal. Everything else goes to my hotkey window.
I also tend to use tmux(1)
for terminal multiplexing, particularly
on servers I manage. I like how iTerm2 integrates pretty well with
tmux(1)
, particularly in how iTerm2 can represent tmux(1)
windows
as native iTerm2 windows and
tabs. Since
it can get confusing, I have a separate color scheme for attached
tmux(1)
windows. Previously, I was a heavy screen(1)
user, and I
still am very familiar with its keybindings — I will
occasionally use it, if tmux(1)
isn’t available, or if iTerm2
doesn’t support integrating with the particular version of tmux(1)
:
I haven’t learned some of the keybindings, so I do get somewhat lost
if iTerm2 doesn’t integrate with it.
Shell Config
There’s a bunch of ways to get zsh(1)
configured the way you
want. In particular, there are several zsh(1)
configuration/module
systems; I’ve chosen to use zplug
,
which was suggested to me by Zak on
Slack when I asked for suggestions. I’ve been
pretty happy with it so far, as there isn’t too much magic in the
config, and my .zshrc
reads in a fairly straightforward way. I’ll
probably post my dotfiles one of these days, when I get around to it.
Getting Around
Although I spend a lot of time in the shell, I have to admit that I don’t write a lot of shell scripts; a lot of my shell use has been ephemeral, and most of the time I only need to do something once or twice. For those times I need to execute something repeatedly, an alias usually suffices.
However, I do have a function installed in my .zshrc
for prj
,
which allows me to cd
to a project directory, with shell completion:
in my home directory, I always have a ~/projects
directory, which
contains the code of all my projects, organized the way I like
it. Under ~/projects
, I have one directory per category of projects:
work, personal, skunk works, etc.; each project would then be under
one of those categories.
It’s pretty straightforward to just cd
to a particular project
directory with Tab completion, but I’m often somewhere in the disk
hierarchy, so I have that particular shell function for going
around. The function itself isn’t anything too spectacular— I
just use the CDPATH
environment variable to nudge cd
to jump to a
directory under the ~/projects
hierarchy. The way CDPATH
works
with cd
is it provides an alternative list of paths that cd
will
use to find the directory to change to, instead of just the current
working directory.
I’ve been using that shell function for quite a while now, and you can
take a look at this gist
to get an idea of how I use it; that gist is from my bash(1)
config
way back when: the equivalent zsh(1)
function is actually shorter,
but similar, and most of the differences are due to the differences
in completion support.
Most of the time, as I said, I don’t have shell scripts or functions
that I write once and reuse; I instead tend to rely heavily on shell
history to get around. C-r
(^R
, or Control-R) is my friend, and
often I have a good memory of commands or pipelines I’ve executed
before. If all else fails, I can always do history 0 | grep $foo
or
history 0 | less
to search for something in my command history
manually — I have zsh(1)
configured to keep history, with the
defaults from the history
module from prezto
.
The other thing I find myself using quite heavily to navigate are
shell globs: those things that allow you to specify files whose name
matches particular patterns. Most people are familiar with the *
and
?
globs, but I learned (only a couple years ago, I might add) the
use of the {}
glob: it allows you to specify a set of
substitutions. For instance, I sometimes have a need to make a
temporary backup copy of a particular file:
$ cp UserService.java{,.bak}
which copies UserService.java
to UserService.java.bak
. The glob
{foo,bar}
expands to foo
bar
; so foo{,.bak}
expands to foo
foo.bak
.
I find it useful especially when I’m on connected via ssh using my phone, as it’s often hard to use Tab completion in such a cramped space, and in those situations I find it easier to just type in what I intend to do anyway.
Pipelines Etcetera
There are a few commands I use pretty heavily in my day-to-day shell
use. One of the go-to commands I use especially for pipelines is
xargs(1)
, as it allows me to stitch together the arguments to
another command from the output of a different shell command. For
instance, one of my most recent use of xargs(1)
is to count the
lines of all of the checked-in code inside the git
repository of my
current project:
$ git ls-files | xargs wc -l
My current project uses git
submodules, so I often find myself
needing to look for all references of a string, particularly within
Java source files:
$ git ls-files --recurse-submodules -- '*.java' |
xargs wc -l
I also find myself using pipelines in general for a bunch of stuff,
and not just with xargs
. Pipelines are the thing to learn when in
the shell, if you ask me, as it allows you to stitch things together.
Another command I exercise a lot is less(1)
: it’s been my default
pager since the first time I used a Linux machine. I often use it
instead of tail -f
to tail through a log file: hitting F
(Shift-F)
in less(1)
tells it to follow the file, similar to tail(1)
s -f
switch. Using less
in this mode has the added advantage of you being
able to interrupt following the end of the file you’re looking at to
search backwards for something that’s just scrolled by, or to simply
scroll backwards: you’re already in the pager, so it’s pretty
convenient.
Finally, grep(1)
is always my go-to for grovelling through files to
find that one string somewhere; YMMV though, as you might want to
check out the alternatives to grep(1)
, such as The Silver
Searcher or
Ack— I use grep(1)
purely out of
inertia, but that doesn’t mean you should too. I also occasionally use
sed(1)
to do some global string replacements, so grep(1)
feels
more natural for me to use.
Anyway, I hope you find some of the things I do on the shell quite useful. I don’t claim to be an expert though, and there’s a crapton of things I think I can still learn to do. If you have any suggestions, send me an email or tweet: I’d love to hear some of the things you do to make your shell work for you.
Previously: Workman's Tools