I have a bit of a problem. Whenever I find myself running the same command(s) in the terminal within a short timeframe1, I experience a strong urge to reduce the number of characters I need to type. In some cases, the dynamic parts of the command(s) are a tad too unwieldy to easily abstract into a reusable form2. That's usually enough to stifle the urge. However, the other 146 times, I end up adding an alias/function to my shell config; but who's counting.
Admittedly, I don't use all of my custom aliases/functions regularly. Some I barely recognise as they haven't been of use in years. But there are about a handful or so that see active usage multiple times a day.
I've been using a tool called atuin
for the last 16 months to keep track of my shell history. In that time these are my top 10 commands:
[▮▮▮▮▮▮▮▮▮▮] 7723 gs
[▮▮▮ ] 3012 gl
[▮▮▮ ] 2698 gap
[▮▮▮ ] 2506 wm
[▮▮ ] 2189 nsd
[▮▮ ] 2087 wl
[▮▮ ] 1843 ll
[▮▮ ] 1772 cd
[▮▮ ] 1685 wq
[▮ ] 1528 just
cd
is a built-in command, just
is a command runner similar to make
, and ll
is arguably the most well-known shell alias3. The rest are my own bespoke custom aliases. And yes, I'm struggling to come to terms with the fact that I run gs
(my alias for git status
) roughly 28 times a day4.
Recently, I added zsh
completions to 2 of my custom shell functions for different reasons, which I'll go into below:
Avoiding Typos
I've been using a CLI time tracker called watson
for a number of years. To start tracking a project, I use the following command:
watson start <project>
or more precisely, as indicated by my shell history statistics, I use the wm
alias:
wm <project>
<project>
can be any string, which is only great if you can type correctly most of the time. I like to track miscellaneous activities such as triaging tickets, and catching up on email and Slack messages under a project I call "prep". The sheer number of times I typed wm prpe
instead of wm prep
was high enough that I needed to come up with a solution.
I implemented completions for my wm
function meaning that I can now start tracking the "prep" project by typing wm p
and then hitting Tab followed by Enter.
The completion is fairly simple given that there is only one positional argument (the name of the project):
#compdef wm
_wm() {
local -a projects
projects=(
'discover:Work on the Discover feature'
'meeting:Start a new meeting'
'prep:Miscellaneous work'
'review:Code review'
'user-library:Work on the User Library feature'
)
_describe 'projects' projects && ret=0
}
_wm "$@"
This completion resides in a file called _wm
. The path of this file's parent directory must be added to the FPATH
environment variable for the completions to work. Personally, I place all my completion files in the ~/.config/zsh/completions
directory, so I set the FPATH
as follows:
export FPATH="$FPATH:$HOME/.config/zsh/completions"
Memory Aid
Publishing npm packages is fairly simple on paper. My nv
alias reflected that:
alias nv="npm version"
Every few weeks or months, I'd want to publish a beta version of a package. Each time, I would search online for a guide on using npm version
because I couldn't quite recall the flags I needed to pass. At times, I even resorted to manually bumping the version number in package.json
.
Armed with my recently-acquired, albeit basic, knowledge of zsh completions, I was eager to make the hard-to-recall flags a thing of the past. But before I could do that, the alias needed to be upgraded into a much more refined function:
nv () {
local identifier="$1"
local prerelease_tag="$2"
[ -z $identifier ] && echo "Usage: nv <identifier> [prerelease_tag]" && return 1
[ -z $prerelease_tag ] && npm version $identifier && return 0
if [[ $identifier = "major" && $(npm pkg get version) =~ "\.0\.0-${prerelease_tag}\.[0-9]+\"$" ]] ||
[[ $identifier = "minor" && $(npm pkg get version) =~ "[1-9][0-9]*\.0-${prerelease_tag}\.[0-9]+\"$" ]] ||
[[ $identifier = "patch" && $(npm pkg get version) =~ "[1-9][0-9]*-${prerelease_tag}\.[0-9]+\"$" ]]
then
npm version prerelease
else
npm version "pre${identifier}" --preid="${prerelease_tag}"
fi
}
I saved the completion to a file called _nv
that I made sure can be found using the FPATH
environment variable. Since my nv
function accepts 2 arguments, the first of which is mandatory, the completion first suggests the 3 semver identifiers ("major", "minor" and "patch") and then 2 optional prerelease tags ("beta" and "rc") for me to choose from:
#compdef nv
_nv() {
local line state
_arguments -C \
"1: :->id" \
"*::arg:->tag"
case "$state" in
id)
_values "Version identifier" \
"patch" \
"minor" \
"major"
;;
tag)
_values "Pre-release tag" \
"beta[]" \
"rc[]"
;;
esac
}
Below is the nv
function in action:
Footnotes
-
The timeframe varies from a few hours to a few weeks. That's still short, right? ↩︎
-
In those cases, I'm perfectly fine searching through my shell history. ↩︎
-
Don't quote me on that. ↩︎
-
It seems like adding new aliases/functions to my shell isn't my only problem... ↩︎
- Sources