The shell stack we would set up today
Four tools that make shell scripting survivable, and the point at which you should stop writing shell entirely.
Shell is the language you write least deliberately and run most often. A small amount of tooling changes it from a source of 3am surprises into something you can trust.
Install these#
shellcheck — non-negotiable#
The single highest-value tool in this ecosystem. It catches unquoted expansions, wrong test operators, useless cat, subshell variable loss, and about three hundred other things — including nearly every mistake in generated shell.
brew install shellcheck # or apt install shellcheck
shellcheck -S warning scripts/*.shWire it into your agent's post-edit hook and generated scripts get fixed before you see them. See shell scripts as agent guardrails.
shfmt#
Formatting, so diffs are about substance.
shfmt -w -i 2 -ci -bn scripts/bats for anything important#
If a script does something you would be upset to get wrong, test it.
@test "refuses to deploy without a target" {
run ./deploy.sh
[ "$status" -eq 2 ]
[[ "$output" == *"usage"* ]]
}trash instead of rm#
brew install trashThen alias rm to it in your interactive shell. Not in scripts — in your shell, where the accidents happen. Recoverable deletes cost nothing and have saved a great many afternoons.
Modern replacements worth having#
None of these are required and all of them make the terminal better. All free.
| Old | New | Why |
|---|---|---|
grep | ripgrep (rg) | much faster, respects .gitignore |
find | fd | sane syntax, fast |
cat | bat | syntax highlighting, paging |
ls | eza | git status in the listing |
cd | zoxide | jumps to frecent directories |
sed/awk for JSON | jq | the right tool, and agents use it correctly |
sed/awk for YAML | yq | same |
du | dust | readable output |
jq deserves particular mention: it is how agent hooks parse their input, and generated jq is generally correct because the language is small.
Terminal#
Warp's case is the block model — each command and its output are a discrete unit, which makes agent-driven sessions far easier to follow and to scroll back through. There is a good free tier.
Ghostty, WezTerm, Alacritty and Kitty are all excellent, all free, and we earn nothing from any of them. If you are happy with your terminal, this is not a problem worth solving.
Where to run it#
For a box to run scripts, cron jobs and self-hosted agent runners on, Hetzner is the price-performance answer and has been for years.
Droplets if you want the better dashboard, the managed databases and the $200 of credit to experiment with.
When to stop writing shell#
This is the most useful advice on the page.
Stop at about 100 lines, or at the first of these:
- You need an array of anything other than strings
- You need to parse JSON that is more than one
jqcall - You need error handling more subtle than "exit non-zero"
- You are writing a function that returns a value
- You have written
eval
At that point, Python or Go will be shorter, clearer, testable, and much easier to review. A 400-line bash script is a program written in a language that does not have data structures — and generated shell at that length is genuinely hard to verify.
The shell's job is orchestration: run these commands, in this order, stop if one fails. It is very good at that, and it should mostly do only that.
The script header, one more time#
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
trap 'echo "failed at line $LINENO: $BASH_COMMAND" >&2' ERRIf you take one thing from this site, take those four lines.
Common questions#
Is set -e enough on its own?#
No — it has well-known gaps, particularly in pipelines and inside conditionals. set -Eeuo pipefail together with an ERR trap covers far more, and set -u in particular prevents the unset-variable-in-a-path accident that causes the worst outcomes.
bash or POSIX sh?#
bash unless you genuinely need to run on Alpine or a BSD without it, in which case sh and shellcheck -s sh to enforce it. Writing POSIX-only by default costs you arrays and [[ ]] for a portability you probably do not need.
Should I use zsh or fish for scripts?#
No. Use them interactively if you like them; write scripts in bash with an explicit shebang. Scripts get run by things that are not your shell.
Get the Bash agent pack
A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for Bash. One email, then occasional updates when the tooling shifts. No course pitch.
AGENTS.md now — no email needed.
Disclosure: some links on this page are affiliate links. If you buy something through one, we earn a commission at no extra cost to you. We only list tools we would tell a friend to use, and we say so when we have not used something ourselves. This is how the site stays free and ad-light.