README
¶
is: an inspector for your environment
Introduction
is is an inspector for your environment. I made it for my
dot-files. is tries to make it just a
little bit easier to run commands which rely on a specific OS or a specific CLI
version. Aside from the docs below, this quick
introduction
can get you up and running in a hurry.
Is the minimum version of this tool available?
is cli version tmux gt 3.2 && echo 🥳 || echo 😢
Is this the target Operating System?
(is os name eq darwin && echo 🍏 ) ||
(is os name eq linux && echo 🐧) ||
echo 💣
Check the OS with a regex
is os name like "da\w{4}" && echo 🍏
Is this a recent macOS?
is os version-codename in ventura,monterey
Who am I?
is cli output stdout whoami eq olaf
Do we have go? Then install goimports
is there go && go install golang.org/x/tools/cmd/goimports@latest
What's the version of bash?
$ is known cli version bash
5.2.15
What's the major version of zsh?
$ is known cli version --major zsh
5
Has gofumpt been modified in the last week?
is cli age gofumpt lt 7 d
Has a file been modified in the last hour?
is fso age ./stats.txt lt 1 h
echo the OS name
$ echo "i'm on $(is known os name) for sure"
i'm on darwin for sure
Get some debugging information about the OS
macOS:
$ is known os name --debug
{
"name": "darwin",
"version": "13.4",
"version-codename": "ventura"
}
darwin
Linux:
is known os name --debug
{
"id": "ubuntu",
"id-like": "debian",
"name": "linux",
"pretty-name": "Ubuntu 22.04.2 LTS",
"version": "22.04",
"version-codename": "jammy"
}
linux
Can user sudo without a password?
is user sudoer || echo 😭
Exit Codes are Everything
is returns an exit code of 0 on success and non-zero (usually 1) on
failure. We can leverage this in shell scripting:
In a script:
#!/bin/bash
if is os name eq darwin; then
# This is a Mac. Creating karabiner config dir."
mkdir -p "$HOME/.config/karabiner"
fi
At the command line:
is os name ne darwin && echo "this is not a mac"
Debugging error Codes
In bash and zsh (and possibly other shells), $? contains the value of the
last command's exit code.
$ is os name eq x
$ echo $?
1
$ is os name eq darwin
0
Using a Regex
The like and unlike operators accept a regular expression. We may need to
quote our regex. For instance:
is os name like darw\w
should be
is os name like "darw\w"
We can the debug flag to see how our regex may have been changed by our shell:
$ is os name like darw\w --debug
comparing regex "darww" with darwin
In this case we can see that the unquoted \w is turned into w by the shell
because it was not quoted.
We can also use regexes with no special characters at all:
is os version-codename unlike ventura
Under the Hood
🚨 Leaky abstraction alert!
Regex patterns are passed directly to Golang's regexp.MatchString. We can
take advantage of this when crafting regexes. For instance, for a case
insensitive search:
is cli output stdout date like "(?i)wed"
Top Level Commands
arch
Checks against the arch which this binary has been compiled for.
is arch eq amd64
is arch like 64
Supported comparisons are:
eqneinlikeunlike
We can try is known arch to get the value for our installed binary or run this
command with the --debug flag.
Theoretical possibilities are:
386
amd64
arm
arm64
loong64
mips
mips64
mips64le
mipsle
ppc64
ppc64le
riscv64
s390x
wasm
however, there are available binaries for only some of these values.
cli
age
Compare against the last modified date of a command's file.
is cli age tmux lt 18 hours
Don't let goimports get more than a week out of date.
is cli age goimports gt 7 days && go install golang.org/x/tools/cmd/goimports@latest
Update shfmt basically daily, but only if go is installed.
if is there go; then
if is cli age shfmt gt 18 hours; then
go install mvdan.cc/sh/v3/cmd/shfmt@latest
fi
else
echo "Go not found. Not installing shfmt"
fi
Supported comparisons are:
ltgt
Supported units are:
ssecondsecondsmminuteminuteshhourhoursddaydays
Note that d|day|days is shorthand for 24 hours. DST offsets are not taken
into account here.
The --debug flag can give us some helpful information when troubleshooting
date math.
version
Compare versions of available commands. Returns exit code of 0 if condition is true and exit code of 1 if condition is false.
is cli version go gte 1.20.4 || bash upgrade-go.sh
If we want to match only on part of a version, we can do that by specifying
which segment of the version we want to match on, where the pattern is
major.minor.patch. Given a version number of 1.2.3 that would make 1 the
major version, 2 the minor version and 3 the patch version. If the
version number does not include a patch, then it is assumed to be zero.
Likewise for a missing minor segment.
The segment flags are --major, --minor and --patch. They are mutually
exclusive, only one of these flags can be passed per command.
Let's rework the example above to say that any version of go with a minor
version >= 20 means we don't need to upgrade.
is cli version --minor go gte 20 || bash upgrade-go.sh
We could also express this in the following way, if we want to match on the major version as well.
is cli version go gte 1.20
Supported comparisons are:
ltlteeqgtegtinnelikeunlike
output
Run an arbitrary command and compare the output of stdout, stderr or
combined. Whitespace is automatically trimmed from the left and right of
output before any comparisons are attempted. So, we don't need to worry about
trimming output with leading spaces like this:
cat README.md | wc -l
847
The format for this command is:
is cli output \
[stdout|stderr|combined] \
some-command \
--arg foo --arg bar \
[lt|lte|eq|gte|ne|like|unlike] "string/regex to match"
is cli output stdout date like Wed
Case insensitive:
is cli output stdout date like "(?i)wed"
ssh prints its version to stderr.
is cli output stderr ssh --arg="-V" like 9.0p1
combined allows us to match on stdout and stderr at the same time.
is cli output combined ssh --arg="-V" like 9.0p1
Optional argument to command. Can be used more than once.
Let's match on the results of uname -m -n.
is cli output stdout uname --arg="-m" --arg="-n" eq "olafs-mbp-2.lan x86_64"
If our args don't contain special characters or spaces, we may not need to
quote them. Let's match on the results of cat README.md.
is cli output stdout cat --arg README.md like "an inspector for your environment"
Optional argument to command. Defaults to optimistic. Because comparisons
like eq mean different things when comparing strings, integers and floats, we
can tell is what sort of a comparison to perform. Our options are:
- float
- integer
- string
- version
- optimistic
optimistic will first try a string comparison. If this fails, it will try a
version comparison. This will "Do What I Mean" in a lot of cases, but if we
want to constrain the check to a specific type, we can certainly do that.
is cli output stdout \
bash --arg="-c" --arg="cat README.md | wc -l" \
gt 10 \
--debug --compare integer
Tip: Using pipes
To pipe output from one command to another, we'll need to do something that is
equivalent to: bash -c "some-command | other-command"
To count the number of lines returned by date, we might normally write:
$ date | wc -l
1
Via bash -c:
$ bash -c "date | wc -l"
1
Now, run via is and assert that there really is just one line:
is cli output stdout bash --arg='-c' --arg="date|wc -l" eq 1
Let's make this more succinct. We can make this a little shorter, because is
handles bash -c as a special case:
is cli output stdout "bash -c" -a "date|wc -l" eq 1
Tip: Using Negative Numbers
Passing negative integers as expected values is a bit tricky, since we don't want them to be interpreted as flags.
$ is cli output stdout 'bash -c' -a 'date|wc -l' gt -1
💥 is: error: unknown flag -1, did you mean one of "-h", "-a"?
We can use -- before the expected value to get around this. 😅
$ is cli output stdout 'bash -c' -a 'date|wc -l' gt -- -1
Use the --debug flag to see where comparisons are failing:
is cli output stdout uname --arg="-m" --arg="-n" eq "olafs-mbp-2.lan x86_65" --debug
2023/09/13 23:05:26 comparison "olafs-mbp-2.lan x86_64" eq "olafs-mbp-2.lan x86_65"
2023/09/13 23:05:26 comparison failed: olafs-mbp-2.lan x86_64 eq olafs-mbp-2.lan x86_65
Supported comparisons are:
ltlteeqgtegtinnelikeunlike
👉 Nota bene: because is doesn't know what you're trying to match, it will,
in some cases try to do an optimistic comparison. That is, it will try a string
comparison first and then a numeric comparison. Hopefully this will "do the
right thing" for you. If not, please open an issue.
fso
fso is short for filesystem object (file, directory, link, etc). This command
is very similar to cli age. The difference between cli age and fso age is
that fso will not search your $PATH. You may provide either a relative or
an absolute path.
age
Compare against the last modified date of a file.
is cli age /tmp/programs.csv lt 18 hours
Compare against the last modified date of a directory.
is cli age ~./local/cache gt 1 d
Supported comparisons are:
ltgt
Supported units are:
ssecondsecondsmminuteminuteshhourhoursddaydays
Note that d|day|days is shorthand for 24 hours. DST offsets are not taken
into account here.
The --debug flag can give us some helpful information when troubleshooting
date math.
os
Information specific to the current operating system
version
is os version gt 22
is os version like "13.4.\d"
If we want to match only on part of a version, we can do that by specifying
which segment of the version we want to match on, where the pattern is
major.minor.patch. Given a version number of 1.2.3 that would make 1 the
major version, 2 the minor version and 3 the patch version. If the
version number does not include a patch, then it is assumed to be zero.
Likewise for a missing minor segment.
The segment flags are --major, --minor and --patch. They are mutually
exclusive, only one of these flags can be passed per command.
is os version --major eq 13
Supported comparisons are:
ltlteeqgtegtinnelikeunlike
name
Under the hood, this returns the value of runtime.GOOS, a constant which is
set at compile time. So, is reports on on the OS name which is the target of
build rather than running uname or something like that. This is "good enough"
for my purposes. If it's not good enough for yours, we probably need to add
more build targets.
Available comparisons are:
eqneinlikeunlike
is os name eq darwin
is os name ne linux
is os name in darwin,linux
is os name like darw
is os name like "dar\w{3}"
is os name unlike "foo\d"
Possible values for name:
darwin
linux
Linux only.
is os pretty-name eq "Ubuntu 22.04.2 LTS"
Available comparisons are:
eqneinlikeunlike
Linux only.
is os id eq ubuntu
Available comparisons are:
eqneinlikeunlike
is os id-like eq debian
Available comparisons are:
eqneinlike- `unlike``
is os version-codename eq jammy
Available comparisons are:
eqneinlikeunlike
On Linux, the value for version-codename is taken from /etc/os-release. For
Macs, the values are mapped inside this application.
Possible values for Mac:
- ventura
- monterey
- big sur
- catalina
- mojave
- high sierra
- sierra
- el capitan
- yosemite
- mavericks
- mountain lion
there
Returns exit code of 0 if command exists and exit code of 1 if command cannot be found.
is there tmux && echo "we have tmux"
user
sudoer
is user sudoer && sudo apt-get install ripgrep
Returns 1 if the current appears to be able to sudo without being prompted
for a password.
This is useful for scripts where we want to install via sudo, but we don't
want the script to be interactive. That means we can skip installing things
that require sudo and handle them in some other place.
known
Prints known information about a resource to STDOUT. Returns 0 on success
and 1 if info cannot be found.
arch
Prints the value of golang's runtime.GOARCH. Note that this is the arch that
the binary was compiled for. It's not running uname under the hood.
Theoretical possibilities are:
386
amd64
arm
arm64
loong64
mips
mips64
mips64le
mipsle
ppc64
ppc64le
riscv64
s390x
wasm
however, there are available binaries for only some of these values.
os
Details specific to the current operating system.
Under the hood, this returns the value of runtime.GOOS, a constant which is
set at compile time. So, is reports on on the OS name which is the target of
build rather than running uname or something like that. This is "good enough"
for my purposes. If it's not good enough for yours, we probably need to add
more build targets.
$ is known os name
linux
Possible values for name:
darwin
linux
Linux only.
$ is known os pretty-name
Ubuntu 22.04.2 LTS
Linux only.
$ is known os id
ubuntu
Linux only.
$ is known os id-like
debian
$ is known os version
22.04
$ is known os version --major
22
$ is known os version --minor
04
$ is known os version --patch
0
Please see the docs on os version for more information on --major,
--minor and --patch.
$ is known os version-codename
jammy
cli version
$ is known cli version tmux
2.7
$ is known cli version --major tmux
2
$ is known cli version --minor tmux
7
$ is known cli version --patch tmux
0
Please see the docs on os version for more information on --major,
--minor and --patch.
--debug
Print some debugging information to STDOUT.
$ is os name eq darwins --debug
Comparison failed: darwin eq darwins
--help
Top level command help.
Usage: is <command>
Flags:
-h, --help Show context-sensitive help.
--debug turn on debugging statements
--version Print version to screen
Commands:
arch <op> <val>
Check arch e.g. "is arch like x64"
cli version <name> <op> <val>
Check version of command. e.g. "is cli version tmux gte 3"
cli age <name> <op> <val> <unit>
Check last modified time of cli (2h, 4d). e.g. "is cli age tmux gt 1 d"
cli output <stream> <command> <op> <val>
Check output of a command. e.g. "is cli output stdout "uname -a" like
"Kernel Version 22.5"
fso age <name> <op> <val> <unit>
Check age (last modified time) of an fso (2h, 4d). e.g. "is fso age
/tmp/log.txt gt 1 d"
known arch [<attr>]
Print arch without check. e.g. "is known arch"
known os <attribute>
Print without check. e.g. "is known os name"
known cli <attribute> <name>
Print without check. e.g. "is known cli version git"
os <attribute> <op> <val>
Check OS attributes. e.g. "is os name eq darwin"
there <name>
Check if command exists. e.g. "is there git"
user [<sudoer>]
Info about current user. e.g. "is user sudoer"
Run "is <command> --help" for more information on a command.
subcommand --help
Usage: is os <attribute> <op> <val>
Check OS attributes. e.g. "is os name eq darwin"
Arguments:
<attribute> [id|id-like|pretty-name|name|version|version-codename]
<op> [eq|ne|gt|gte|in|like|lt|lte|unlike]
<val>
Flags:
-h, --help Show context-sensitive help.
--debug turn on debugging statements
--version Print version to screen
--major Only match on the major OS version (e.g. major.minor.patch)
--minor Only match on the minor OS version (e.g. major.minor.patch)
--patch Only match on the patch OS version (e.g. major.minor.patch)
--version
Print current version of is
is --version
Installation
Choose from the following options to install is.
- Download a release
- Use
go install
go install github.com/oalders/is@latestgo install github.com/oalders/is@v0.5.0
- Use ubi
#!/usr/bin/env bash
set -e -u -x -o pipefail
# Or choose a different dir in your $PATH
dir="$HOME/local/bin"
if [ ! "$(command -v ubi)" ]; then
curl --silent --location \
https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh |
TARGET=$dir sh
fi
ubi --project oalders/is --in "$dir"
Bonus: Easier Version Parsing of Available Tools
Forget about the remembering the different version incantations of command
line tools. Don't make up regexes to extract the actual version number.
Go (version)
$ go version
go version go1.20.4 darwin/amd64
$ is known cli version go
1.20.4
Perl (--version)
$ perl --version
This is perl 5, version 36, subversion 1 (v5.36.1) built for darwin-2level
Copyright 1987-2023, Larry Wall
Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.
Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at https://www.perl.org/, the Perl Home Page.
$ is known cli version perl
v5.36.1
tmux (-V)
$ tmux -V
tmux 3.3a
$ is known cli version tmux
3.3a
Documentation
¶
Overview ¶
package main contains the api for the CLI
Package main contains the logic for the "cli" command
Package main contains the logic for the "fso" command
package main contains the logic for the "known" command
The main package is the command line runner for the is command.
This file handles OS info parsing ¶
package main contains the logic for the "there" command
Package main contains the logic for the "user" command
Directories
¶
| Path | Synopsis |
|---|---|
|
package attr exports attribute constants
|
package attr exports attribute constants |
|
package compare compares versions
|
package compare compares versions |
|
package main contains macOS logic
|
package main contains macOS logic |
|
Package os handles OS info parsing
|
Package os handles OS info parsing |
|
Package parser contains output parsers
|
Package parser contains output parsers |
|
package reader contains ini file reader logic
|
package reader contains ini file reader logic |
|
package version creates version objects from strings
|
package version creates version objects from strings |