Run Shell Scripts Through ShellCheck First
Your AI assistant writes bash with the same quoting bugs you do. It learned shell from the same scripts that taught humans to write rm -rf $DIR/ — and just like a human, it will hand you code that works on the happy path and detonates on a filename with a space. The fix isn't better prompting. It's a pipeline: lint, then format, then test — in that order.
Why lint before test
Most shell bugs aren't logic bugs. They're static defects: an unquoted variable expansion, a [ ] comparison that breaks on empty strings, a bashism inside a #!/bin/sh script. A behavior test can pass 100 times while an unquoted variable waits for the first path with a space in it.
That's the trap with AI-generated scripts specifically: the model produces something plausible, you run it once, it works, you ship it. A linter reads the script the way the shell will parse it, not the way it happened to run — and it catches the whole defect class at once instead of one crash at a time.
ShellCheck is the standard tool here — a static analysis tool for sh/bash scripts, recommended by the Google Shell Style Guide "for all scripts, large or small." Every finding has a wiki page with the exact bad pattern and the fix, which also makes it perfect paste-back material for your AI assistant.
The three-stage pipeline
| Stage | Tool | Command | Catches |
|---|---|---|---|
| 1. Lint | ShellCheck | shellcheck script.sh | Quoting, portability, dead code, misspelled vars |
| 2. Format | shfmt | shfmt -l -w script.sh | Inconsistent indentation and layout |
| 3. Behavior | Bats | bats test/ | What the script actually does |
Two ShellCheck findings pay the whole bill by themselves:
- SC2086 — "Double quote to prevent globbing and word splitting."
echo $1splits on whitespace and expands globs;echo "$1"doesn't. This is the single most common AI-generated shell bug. - SC2154 — "var is referenced but not assigned." A misspelled variable in bash isn't an error — it's an empty string. Silent, and lethal in a
rmpath.
Then shfmt normalizes formatting (-l lists files that would change, -w rewrites in place), and Bats — a TAP-compliant testing framework for Bash 3.2+ — covers behavior with .bats files, a run helper for invoking commands, and setup/teardown hooks.
Test the paths the AI never imagines
- Paths with spaces. Run the script from
"/tmp/my dir/"and pass it filenames with spaces. This is where every SC2086 the linter didn't catch surfaces. - Missing environment variables. Unset the variables the script assumes exist and confirm it fails loudly instead of operating on empty strings.
- Empty output. Pipe it nothing. Loops over
$(cmd)behave very differently on zero results.
Power-user moves
- Gate CI on it. ShellCheck uses exit codes canonically, so
shellcheck scripts/*.shin CI fails the build on findings.--severity=warninglimits output to errors and warnings when you're adopting it on a legacy repo. - Machine-readable output for agents. ShellCheck emits JSON and GCC-compatible formats — feed the JSON straight back to your AI assistant and ask it to fix each finding by code. The wiki page per code (e.g.
shellcheck.net/wiki/SC2086) is ideal context. - Lint in the editor. ShellCheck integrates with VS Code, Vim, Emacs, and Sublime, so AI-suggested shell gets flagged the moment it lands in the buffer — before you even save.
- No install needed to start. Paste a script into shellcheck.net for instant results.
Resources
- ShellCheck — GitHub repository and docs
- SC2086: Double quote to prevent globbing and word splitting
- SC2154: var is referenced but not assigned
- shfmt — shell formatter (mvdan/sh)
- Bats-core documentation
- Google Shell Style Guide
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.