2024-12-02 Why Bash

Why Bash for MyCmd?

I have been asked, “Why Bash?” when describing MyCmd and its implementation.

To be frank, much of it is inertia – I’ve been writing shell scripts for over 25 years. I have attempted to write my own “standard library” for Bash several times over the years, MyCmd is just my latest (and hopefully, last) incarnation of this idea.

MyCmd’s Use Cases

It is important to understand the use cases for a piece of software to be able to make the right technology choices. For me, MyCmd is a tool to glue together other command line tools. Here are a few of the things I’ve written with it (and will rewrite with my latest version, when complete):

As you can see, most of my use cases for MyCmd commands are wrappers around command line tools. Because of this, I want to optimize for easily executing external programs.

For example, see this function for updating my SSH keychain:

set -o nounset -o errexit -o errtrace -o pipefail

function daily.update_ssh_keychain() {
    # First, remove any expired SSH credentials
    ssh-add -D

    # Then re-add work and personal credentials, if present
    local identity

    for identity in id_ecdsa personal_id_rsa; do
        if [[ -e "${HOME}/.ssh/${identity}" ]]; then
            ssh-add "${HOME}/.ssh/${identity}"
        fi
    done
}

A similar function in Python, that replicates the above behavior:

import subprocess
from pathlib import Path

def update_ssh_keychain():
    subprocess.run(["ssh-add", "-D"], check=True)

    for identity in ["id_ecdsa", "personal_id_rsa"]:
        keyfile = Path(f"~/.ssh/{identity}").expanduser()

        if keyfile.exists():
            subprocess.run(["ssh-add", keyfile.as_posix()], check=True)

Although this simple example may not seem like it adds a lot of friction, but with a lot of calls to external programs, the calls to subprocess.run end up being laborious.

Additionally, many of the commands that I implement with MyCmd start out of me just fiddling around in an interactive shell. It becomes easy, therefore, to just take that code and turn it into something more robust and reusable with MyCmd.

What about POSIX and other shells?

If I insist on writing shell scripts, why not target POSIX shell or Zsh instead of Bash? Because, in fact, I actually have been using Zsh everywhere for my interactive shell for a few years. Why not use these? It comes down to a few things:

I do want to also call out that MyCmd is not pure shell. I do rely on a few external tools, including those from GNU coreutils. I have built-in support in MyCmd to handle finding and executing tools in a cross platform way.

Alternatives

I am not tied to Bash. If I find a language or tool that I think fits my goals, I would happily rewrite MyCmd in that.

I have explored a few languages that transpile to Bash, such as Amber, but none of them have felt mature enough or given me enough advantage over plain Bash to warrant their use.

I have also become interested in rewriting languages like Nova and exploring if they could be used to write CLI tools either directly or by transpiling to Bash.

Why Not Bash?

I will be the first to admit that Bash is not the friendliest language. I often have to resort to writing my own tools (like the currently on the back burner bashdoc) to get features I want. Tools like shellcheck are critical to writing error-free Bash code; though there are some bugs such as issue 1225 that I run into often because of my heavy use of array references.

There are well-documented pitfalls to using the language. A few off of the top of my head:

Conclusion

Ultimately, I have developed a style of writing Bash that I like. It has made it easy to write the code that I want and easily create the tools and automation for my projects with it. I fully admit my choice of Bash for MyCmd is fully influenced by inertia and habit of decades of writing shell scripts. I have built useful tools with it, and I am having fun doing so, and I think that’s the most important part.