$ (is os name eq darwin && echo 🍏 ) ||
(is os name eq linux && echo 🐧) ||
echo 💣
$ 🍏
In a hurry? Jump straight to the README or install is now.
Motivation#
Many years ago, before I switched to 100% remote work, I sat beside a young kid who had recently graduated from University. I picked up a lot of command line tricks from him, mostly by looking over his shoulder. I learned two big things from him:
vim
is highly configurable- It’s helpful to keep your config files in a
git
repository
The kid had a neat way of organizing his stuff and by stuff I mean “dot files”. I copied some of his setup and, over the years, I have customized it a lot. My dot files get installed on Macs (sometimes with Apple silicon and sometimes not), Ubuntu VMs, Raspberry Pi and most recently on a Chromebook (so, Debian). I have different needs for different environments, but I want my dot files to handle as much of this as possible. Also, I’d like to keep them in public and on GitHub.
All this means that I have to do a few things to ensure everything gets installed in the right way and on the right machine.
So, you’re probably thinking that I just moved my dot files to Ansible configuration files.
I did not.
Right, but if I were sensible I would use one of the cool dot file frameworks to deploy to my various machines.
I did not.
Surely I’m not just using a bunch of bash
scripts?
Yes, I am.
Now, this may not seem sensible to you, but my reasoning is:
- I want the files to be installable everywhere and I want the repo to
basically bootstrap itself, so if
bash
andgit
are available, things should mostly just work - I want my logic to run quickly, which can be hard to do with Ansible
- I do not want to be constrained by someone else’s way of doing things
- I do want to mess around with shell scripts
- Don’t tell me what to do
Working within these parameters I’m able to do most of the things that I need
to do, but some things seem hard. For instance “do A if we’re on a Mac but do B
on Ubuntu (but only certain versions) or do B on Debian, but also certain
versions. At some point doing all of this in bash
feels kind of tedious. I
wondered if I could write a tool to give me some of these answers in a more
elegant way.
I eventually settled on Go as the implementation language for the tool and
kong as the CLI arg parser. After messing
around with kong
, I realized that I could probably abuse commands and
subcommands to come up with a CLI that reads (at least a little bit) like
English. The end result is: is.
Recipes#
Let’s dive right in.
Sometimes you don’t know if a binary is available. Let’s do a git clone
only
if git
is available.
is there git && git clone https://github.com/oalders/is.git
Likewise, let’s install bat
via homebrew
, but only if we’re on a Mac.
is os name eq darwin && brew install bat
We could further expand this and only install via brew
if it’s available:
is os name eq darwin && is there brew && brew install bat
Do we have a perl
with a version >= 5.34 in our $PATH
?
is cli version perl gte 5.34 && perl -E 'say "TIMTOWDI"'
Let’s express this by checking the minor version specifically:
is cli version perl --minor gte 34
Maybe we can support a range of versions:
is cli version perl --minor in 34,36,38
Just print the codename of this OS version:
$ is known os version-codename
ventura
Just print the system architecture:
$ is known arch
amd64
Try to upgrade ubi if it’s older than 7 days.
is cli age ubi gt 7 days && ubi --self-upgrade
Am I running as the correct user?
is cli output stdout whoami eq olaf
Is today Tuesday?
is cli output stdout date like Tue
Same, but without a regex:
is cli output stdout date --arg='+%a' eq Tue
Is today within Mon-Wed?
is cli output stdout date --arg='+%a' like 'Mon|Tue|Wed'
Same, but without a regex:
is cli output stdout date --arg='+%a' in Mon,Tue,Wed
Is this README.md greater than 800 lines long?
is cli output stdout 'bash -c' -a 'cat README.md|wc -l' gte 800
Are we on the main
branch in this git
repository?
is cli output stdout git --arg=branch --arg='--show-current' eq main
Check if git
supports branch --show-current
before running the previous command:
is cli version git gte 2.22 &&
is cli output stdout git --arg=branch --arg='--show-current' eq main
Use a regex to match an SSH
version. Note that ssh -V
prints to STDERR
.
is cli output stderr ssh --arg='-V' like 9.0p1
The above is essentially the same as the much shorter:
is cli version ssh like 9.0p1
Get the version of a CLI and do something with it.
$ version=$(is known cli version go) && echo $version
1.20.6
This is somewhat easier than parsing a version out of:
$ go version
go version go1.20.6 linux/amd64
In a script, exit early if it’s a Raspberry Pi.
if is os id eq raspbian; then
exit 0
fi
At the command line, update the go
binaries that vim-go
needs:
is there go && nvim +:GoUpdateBinaries +qa
Same, but ensure nvim
is installed:
is there go &&
is there nvim &&
nvim +:GoUpdateBinaries +qa
Same, but install plugins before :GoUpdateBinaries
in order to ensure that
vim-go
has been installed:
is there go &&
is there nvim &&
nvim +:PlugInstall +:GoUpdateBinaries +qa
Same, but ensure we have at least version 1.20 of go
:
is there go &&
is cli version go gte 1.20 &&
is there nvim &&
nvim +:PlugInstall +:GoUpdateBinaries +qa
Now a more complex example. I want to install the nightly wezterm
build. On
macOS, I tend to use homebrew
, but on Linux I’ve chosen to install the .deb
files. To complicate the Linux example, I don’t want to re-install the nightly
wezterm
if I (for whatever reason) re-run my dot files installer. So, if
wezterm
has been installed within the last 18 hours, we won’t try installing
it again. (See if is cli age wezterm lt 18 hours
.)
The download URLs look like:
https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu20.04.deb
and
https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu22.04.deb
1#!/usr/bin/env bash
2
3set -e -u -o pipefail
4
5install_for_linux() (
6 sudo apt install -y libxcb-image0 libxkbcommon-x11-0
7 version=$(is known os version)
8 file=wezterm-nightly.${1}${version}.deb
9
10 cd /tmp || exit 1
11 url=https://github.com/wez/wezterm/releases/download/nightly/$file
12 curl --location --output "$file" "$url"
13 sudo dpkg -i "$file"
14)
15
16if is os name eq darwin; then
17 if is there wezterm; then
18 brew upgrade homebrew/cask/wezterm
19 else
20 brew tap wez/wezterm
21 brew install --cask wezterm --no-quarantine
22 fi
23elif is os name eq linux; then
24 if ! is user sudoer; then
25 exit 0
26 fi
27 is there wezterm && is cli age wezterm lt 18 hours && exit
28 if is os id eq ubuntu && is os version in 20.04,22.04; then
29 install_for_linux Ubuntu
30 elif is os id eq debian && is os version --major in 10,11; then
31 install_for_linux Debian
32 fi
33fi
Hopefully this brief tutorial gives you an idea of what I had in mind with is. I wrote it entirely for my own needs. I’ve been living with it for a couple of months. For my purposes it has been just fine. Maybe you’ll find it useful as well.