What is has-rs?

has-rs checks for the presence of command-line tools are available on your system (e.g. in your PATH) and reports their installed version. Pass it any number of program names and it will tell you exactly which ones are present and which are not.

Additionally, because a missing (non-optional) program causes it to return with a non-zero exit code, it can also be used in scripts that expect a program to be present, or even a program of a certain version.

It is designed to be a drop-in replacement for the original has bash script (referred to as "has-bash" to reduce confusion) but it also includes some great new features, quality of life improvements, and maybe even some bugfixes.

Some common uses:

  • Guarding a script with a quick dependency check before it runs
  • Committing a .hasrc file so contributors can verify their environment
  • Quickly auditing a fresh machine or container

Installation

Pre-built binaries

Linux and Windows builds are available on the releases page. Download the archive for your platform, extract the has binary, and place it somewhere on your PATH.

Releases are compatible with tools like eget2 that can install binaries directly from Forgejo/Gitea releases.

eget2 --codeberg cat-dev-null/has-rs

Using Cargo

Default — includes --allow-unsafe flag
cargo install --git https://codeberg.org/cat-dev-null/has-rs
Without the --allow-unsafe flag (closest to has-bash)
cargo install --git https://codeberg.org/cat-dev-null/has-rs --no-default-features
Always allow unsafe — no flag or env var required
cargo install --git https://codeberg.org/cat-dev-null/has-rs \
    --no-default-features --features always-allow-unsafe

From source

This project uses just as a task runner. Run just --list to see all available recipes including cross-compilation targets.

git clone https://codeberg.org/cat-dev-null/has-rs
cd has-rs

just build
# or if you prefer to just use cargo
cargo build --release

Usage

Basic

Pass the tools you want to check as arguments:

$ has node npm git curl
 node 20.11.0
 npm  10.2.4
 git  2.43.0
 curl 8.5.0

Missing tools

The exit code reflects the number of tools not found:

$ has node cargo foobar
 node          20.11.0
 cargo
 foobar

$ echo $?
2

Using a .hasrc file

Create a .hasrc file in your project root listing one tool per line. Running has with no arguments in that directory will check all of them.

Example .hasrc:

git
cargo
just
fish
jq
$ has
 git   2.43.0
 cargo 1.77.0
 just  1.24.0
 fish  3.7.0
 jq    1.7.1

In scripts

if has node npm
    echo "environment is ready"
end

Or with the quiet flag to suppress output:

has -q node npm && echo "ready" || echo "missing dependencies"

has-rs Exclusives

Beyond parity with has-bash, has-rs ships a few additional features:

🔓 --allow-unsafe

Attempt to detect unknown tools via --version, as a flag rather than only an env variable.

🔀 Alternates

Check for one tool or another. Only counts as a failure if none are present.

❓ Optional programs

Mark a tool optional with ?; a missing optional tool never increments the exit code.

💬 Inline .hasrc comments

End-of-line comments in .hasrc are stripped, so you can annotate entries inline.

💬 Inline .hasrc comments

End-of-line comments in .hasrc are stripped, so you can annotate entries inline.

Supported programs

Below is a complete list of programs that will be detected without having to use HAS_ALLOW_UNSAFE=y or --allow-unsafe. (Wondering what a program is? Hover over for a tooltip!)

  • ab
  • ack
  • act
  • ag
  • ant
  • apt
  • apt-get
  • aptitude
  • aria2c
  • autojump
  • awk
  • aws
  • bash
  • bats
  • brew
  • brunch
  • bun
  • bundle
  • bzip2
  • bzr
  • cargo
  • ccache
  • clang
  • cmake
  • code
  • codium
  • composer
  • conda
  • consul
  • curl
  • deno
  • docker
  • docker-compose
  • eb
  • emacs
  • file
  • firefox
  • fish
  • g++
  • gcc
  • gcloud
  • gem
  • git
  • gnu_coreutils
  • go
  • gor
  • gpg
  • gpg-agent
  • gpgconf
  • gpg-connect-agent
  • gpgsm
  • gradle
  • grep
  • groovy
  • groovyConsole
  • grunt
  • gulp
  • gunzip
  • gzip
  • has
  • heroku
  • hg
  • htop
  • http
  • hub
  • hugo
  • java
  • javac
  • jq
  • just
  • kotlin
  • lein
  • make
  • mvn
  • mysql
  • nano
  • netlify
  • netlifyctl
  • ninja
  • node
  • nomad
  • npm
  • npx
  • openssl
  • packer
  • perl
  • perl6
  • php
  • php5
  • pip
  • pip3
  • pipenv
  • pnpm
  • podman
  • poetry
  • psql
  • pv
  • python
  • python3
  • R
  • rake
  • rg
  • ruby
  • rustc
  • sbt
  • scala
  • screen
  • sed
  • shellcheck
  • sls
  • sqlite3
  • ssh
  • subl
  • sudo
  • svn
  • tar
  • tee
  • terraform
  • tig
  • tmux
  • tree
  • unar
  • unrar
  • unzip
  • uv
  • vagrant
  • vim
  • vite
  • wget
  • xz
  • yarn
  • yq
  • zig
  • zip
  • zsh

If you think you have a program that is worth adding, feel free to submit an issue!

Differences from has-bash

has-rs aims to be as compatible with the bash version as is reasonable. The list below covers everything that is intentionally different.

Behaviour has-rs has-bash
Help output format clap-style (--help) different custom hand-written help
Version output has 0.2.3 different v1.5.2
Quiet flag -q and --quiet new -q only
.hasrc comments Supports end-of-line new Full-line only
Allowing unknown programs --allow-unsafe new HAS_ALLOW_UNSAFE env var only
Bypassing .hasrc --no-rc new .hasrc is always read
Alternate programs Separated with "|" new Not supported
Optional programs End with "?" new Not supported
Suppressing soft errors --hide-optional new N/A
Backwards compatibility has-bash alias new N/A
Unsafe mode as compile-time default always-allow-unsafe feature new Not available

Additionally, here are the programs that has-rs either newly supports or has improved upon over has-bash (as of v1.5.2):

  • gorfixed (incorrect version number parsed)
  • justnew
  • Rnewish (removed from has-bash v1.5.1)
  • sbtfixed (version number never being reported)
  • shellchecknewish (not yet in a has-bash release)
  • zignew

Release Notes

v0.2.3

  • Style: code now conforms to rust-clippy.
    • This should not actually affect any behavior about the program.

I'm mostly pushing this to see if I can get release signing to work.

But I also renamed the Cargo package from has-rs to has-cli, for publishing to crates.io. That does not affect the program itself, though.

Also, added a step to CI to verify that the version in Cargo.toml matches the version in Cargo.lock.

v0.2.2

  • Bugfix: Alternate programs are now outputted as errors if none are found
    • Note that absent alternates would still contribute to a non-zero exit code, they would just output "-" instead of "✗"
  • Feature: new --hide-optional flag supresses output of absent alternate or optional programs
    • It only suppresses alternates if one is eventually found; if no alternates are found, all will be output as errors

v0.2.1

  • Bugfix: "Usage:" outputting wrong binary name when help is displayed.
  • Feature: Supports new program - zig

A lot of other non-code related work was done since the last release:

  • Release notes are now tracked in the distro in the release_notes directory
  • Releases now build for more architectures
  • Releases now include checksums.txt
  • Online documentation is now published each release (from #4)
    • This is now listed as the "homepage" inside Cargo.toml
  • the .hasrc file used for testing now includes comments for what each program is
  • A "Building" section was added to the Readme
  • A lot of new just recipes were added to the justfile to make compiling and testing locally easier

v0.2.0

The most notable update is getting has-rs up to date with has-bash v1.5.2.

  • Supports new programs (natively, without need for HAS_ALLOW_UNSAFE) :
    • sqlite3
    • groovyConsole
    • npx
    • bun
    • deno
    • vite
    • pipenv
    • poetry
    • uv

Other changes:

  • Fix: Keeps bzip2 from hanging, which was a bug introduced into it at some point
    • Corresponds to the upstream fix I submitted for has-bash as PR #94
    • This fix should make has more resilient in case other programs make the same mistake as bzip2
  • Fix: Wrong character was output for failures when color was disabled ( instead of )
  • Fix: Output on passing multiple color flags will now be less ugly
  • Fix: Programs that exist but cannot be executed (e.g. PermissionDenied, ResourceBusy, OutOfMemory, etc) will no longer cause has-rs to panic
  • Feature: New --no-rc flag that disables reading from the rc file if one exists
  • Feature: Alternates can be specified by separating with the "|" character
  • Feature: Optional programs can be specified by appending a ? to the program name
  • Feature: New --allow-unsafe flag acts like (and supersedes) the HAS_ALLOW_UNSAFE environment variable
  • Feature: Supports other new programs
  • CI: Releases are now compatible with programs that install binaries like eget2 or distillery!
    • Note that distillery does not yet support Codeberg, but I'm working on creating a PR for that! 🤞
  • CI: Releases will now include Linux (musl and GNU) and Windows (x86 and ARM)!
    • Will work on getting Linux ARM soon!
  • Change: has-rs now always captures stderr; this aligns with how has-bash acts, but it might cause behavioral changes in non-officially supported programs
    • I cannot remember quite why I made this difference in the first place tbh

v0.1.0

WARNING: This release is entirely historical. Looking back it is missing some things, has bugs, and generally should not be used.

First release of has-rs!

(Supposed to be a) drop-in replacement for has-bash v1.5.0, with the following functional differences:

  • -q flag also has the long form --quiet
  • -v/--version flag outputs the version, but in the Rust clap standard format
  • .hasrc supports comments not just as whole line but end of line (e.g. jq # needed for tooling)
  • Fixes some programs that has-bash does not work correctly for
    • gor
    • sbt
  • has-bash is an alias for has

Missing functionality

Specifically, at the very least:

  • brew is totally absent
  • sqlite parses its version wrong
  • sls causes a panic?
  • Unknown programs (can?) show "not supported" instead of "not understood"