.bashrc series

Conditional Aliases

A .bashrc that doesn't break on a fresh VM

May 20, 2026 ~5 min
bashportabilityaliases

The single biggest reason engineers don't use a fleet-wide .bashrc is that the file they wrote on their laptop spews errors on every other machine. alias k='kubectl' is harmless. source <(kubectl completion bash) on a box without kubectl prints a noisy error every shell startup. Multiply by ten such hooks and the new VM you SSH into yells at you for half a screen before you even get a prompt.

The fix is one pattern, applied everywhere:

command -v X >/dev/null 2>&1 && # do something

That's the entire trick. command -v succeeds silently if the command exists, fails silently if it doesn't. Combine with && to make the next clause conditional. No error spam on boxes where the tool is missing.

The pattern, applied

# Kubernetes — only sets aliases if kubectl is installed
if command -v kubectl >/dev/null 2>&1; then
    alias k='kubectl'
    alias kgp='kubectl get pods'
    alias kgs='kubectl get svc'
    alias kctx='kubectl config use-context'
    alias kns='kubectl config set-context --current --namespace'
    source <(kubectl completion bash 2>/dev/null) 2>/dev/null
    complete -F __start_kubectl k 2>/dev/null
fi

# Helm — only if helm is installed
if command -v helm >/dev/null 2>&1; then
    alias h3='helm'
    source <(helm completion bash 2>/dev/null) 2>/dev/null
fi

# Docker / Podman — only if either is installed
if command -v docker >/dev/null 2>&1; then
    alias d='docker'
    alias dps='docker ps'
fi
if command -v podman >/dev/null 2>&1; then
    alias p='podman'
fi

Same .bashrc deploys to every box. On the Kubernetes control plane, the kubectl block fires. On the streaming server (no kubectl installed), it silently skips. On the docker host, docker aliases load. Nowhere is there any error output.

Path-conditional blocks

The companion pattern is [ -d /path ] — only load this block if a directory exists:

# StackSmith helpers only on hosts where StackSmith is deployed
if [ -d /opt/stacksmith ]; then
    export STACKSMITH_HOME=/opt/stacksmith
    export STACKSMITH_API="https://stacksmith.mcaster1.com/api/v1"
    [ -x /opt/stacksmith/bin/mc1ctl ] && alias mc1ctl='/opt/stacksmith/bin/mc1ctl'
fi

# Mcaster1 webroot shortcut only on hosts that serve the site
[ -d /var/www/mcaster1.com ] && alias cd-mc1='cd /var/www/mcaster1.com/html'

This lets you keep site-specific shortcuts in the fleet-wide file. The shortcut exists on the host that has the site, doesn't exist anywhere else. The user doesn't have to think about it.

The PATH variant

For PATH additions, you want the same conditional behavior plus deduplication:

__mc1_path_prepend() {
    case ":$PATH:" in *":$1:"*) ;;
    *) [ -d "$1" ] && PATH="$1:$PATH" ;;
    esac
}

__mc1_path_prepend "$HOME/.local/bin"
__mc1_path_prepend "$HOME/bin"
__mc1_path_prepend "/opt/stacksmith/bin"
__mc1_path_prepend "/usr/local/go/bin"
export PATH

The function checks if the directory is already in PATH; if not, and if it exists on disk, it gets prepended. Source the file twice and you don't end up with a PATH containing four copies of ~/.local/bin. Source it on a box without Go installed and the Go path silently doesn't get added.

Why this matters more than people realize

Engineers don't share .bashrc files with their teammates because the standard implicit failure mode is "your .bashrc works for you, mine doesn't work for me." The conditional pattern eliminates that failure mode. The same file, on any Linux box, behaves correctly: the parts that are relevant load, the parts that aren't quietly stay out of the way.

Once your .bashrc is portable, you can commit it to a repo. Once it's in a repo, you can deploy it via Ansible. Once it's deployed via Ansible, you have a fleet-wide bash environment. Single pattern, all the way down.

A .bashrc that errors on a fresh VM is a .bashrc that never gets adopted. Conditional everything.

Next: Stop Putting Secrets in .bashrc.

All .bashrc articles