How to Prompt AI to Write a Bash Script You Can Trust
A step-by-step prompt for getting a safe, readable bash script from AI: the safety header, input rules, dry runs, and how to check it with ShellCheck.
To get a bash script you can trust from AI, state the contract before the task: strict mode on, every variable quoted, no destructive command without a dry-run flag, and the exact environment the script runs in. Then run ShellCheck on the result and execute it against a throwaway folder before it touches anything real.
Bash is unforgiving in a way most languages are not. A typo in a variable name does not raise an error, it expands to nothing, and nothing is a valid argument to many commands. AI writes fluent bash that looks right and still carries these traps, because the traps are invisible in the text.
Why AI-written bash fails in quiet ways
The dangerous failures are not syntax errors. Those show up the first time you run the script. The dangerous ones run successfully and do the wrong thing.
An unset variable expands to an empty string. The line rm -rf "$BUILD_DIR"/* with BUILD_DIR unset becomes rm -rf /*.
Unquoted variables split on spaces, so a file called Q3 report.pdf turns into two separate arguments.
A failing command in the middle of a script is ignored by default, and the script carries on as if it had worked.
A failure on the left side of a pipe is hidden by the success of the command on the right.
Scripts written for one shell run under another. The sh, bash, and zsh shells disagree on arrays, double brackets, and more.
The prompt that sets the contract
Put the rules in the prompt before the task. Models weigh what they read first as the frame, and a task stated last gets solved inside that frame.
You are writing a bash script for Linux, run under bash 5.
Rules the script must follow:
1. Start with #!/usr/bin/env bash, then set -euo pipefail.
2. Quote every variable expansion.
3. Validate every required argument and exit with a usage message if one is missing.
4. Use the ${VAR:?} form on any variable inside a path given to rm, mv, or cp.
5. Any command that deletes or overwrites files must be skipped when --dry-run is passed, and print what it would have done.
6. Use mktemp for scratch files and a trap to clean them up.
7. Print errors to stderr and exit non-zero on failure.
8. Use only commands installed by default on Debian.
Task: rename every .jpeg file in a folder to .jpg, lowercase, with spaces replaced by hyphens. The folder is the first argument.
After the script, list every assumption you made.The last line is the one people skip. Asking for assumptions makes the model say out loud what it guessed, such as what happens to a name collision, and each guess is something you can accept or reject.
What a good result looks like
This is the shape of an acceptable answer to that prompt.
#!/usr/bin/env bash
set -euo pipefail
usage() { echo "usage: $0 [--dry-run] <folder>" >&2; exit 2; }
dry_run=0
if [[ "${1:-}" == "--dry-run" ]]; then dry_run=1; shift; fi
dir="${1:-}"
[[ -d "$dir" ]] || usage
shopt -s nullglob nocaseglob
for f in "$dir"/*.jpeg; do
base="$(basename "$f")"
new="$(echo "${base%.*}" | tr '[:upper:] ' '[:lower:]-').jpg"
if (( dry_run )); then
echo "would rename: $f -> $dir/$new"
else
mv -n -- "$f" "$dir/$new"
fi
doneFour details separate this from a careless version. The ${1:-} form stops strict mode from killing the script before it can print the usage line. The mv -n flag refuses to overwrite an existing file. The double dash ends option parsing, so a file named -rf cannot be read as a flag. And nullglob means an empty folder does nothing instead of looping once over the literal pattern.
The review pass: five checks in five minutes
Do these in order. None needs you to read bash fluently.
Run ShellCheck on the file. It catches most quoting and portability mistakes. Paste each warning back into the chat and ask for a fix, one warning at a time.
Search the script for rm, mv, dd, and any redirect that overwrites. For each one, find the variable it depends on and ask the model what happens when that variable is empty.
Run it with --dry-run against a copy of the real folder and read the output line by line.
Run it twice. A well-behaved script is idempotent, so the second run should change nothing.
Break it on purpose: no arguments, a folder that does not exist, a filename with a space, a filename starting with a dash.
If a check fails, do not patch the script by hand. Paste the failing case into the chat, and ask the model to fix the cause and re-run the same checklist. The habit transfers to any prompt that needs repair: show the failure, do not describe it.
When to ask for a different language
Past roughly 100 lines, or the moment you parse JSON, handle dates, or need real error handling, ask for Python instead. Bash is the right tool for gluing commands together and a poor one for logic. You can bake the decision into the prompt: if this needs more than 80 lines or any data parsing, say so and propose Python before writing anything.
The same discipline applies to other notations where a small mistake changes meaning, for example when you prompt AI to write a regular expression. In both cases the fix is to demand test cases, not to trust a confident answer. Once a script works, write a runbook for it so the next person knows what it touches. If it needs to run on a timer, see how scheduled tasks work in an AI-built app. The wider craft is covered in the prompt engineering guide, and the official Bash reference manual settles any argument about syntax.
FAQ
Can AI write a bash script from scratch?
Yes, and for short scripts it is usually faster than writing one yourself. The risk is not that it cannot do it, but that the result looks correct while hiding quoting and empty-variable problems. The contract prompt above is aimed at exactly those.
Should I ask for sh or bash?
Ask for bash unless you know the script must run on a minimal system such as a basic container, where only sh exists. Bash gives you arrays, double brackets, and safer string handling. If you do need sh, say so in the prompt and ShellCheck will flag any bash-only syntax.
How do I make an AI-written script safe to run on a server?
Test in a copy, use the dry-run flag, and avoid running it as root. Give the script the least access it needs, and keep a backup of anything it modifies. Never paste a script into a root shell that you have not read.
Why does the script work in my terminal but fail in cron?
Cron starts jobs with a very small environment and a short PATH. Ask the model to use absolute paths for every command and to log output to a file, then test the script with env -i to mimic the bare environment.
How did this land?
About the author

Developer Advocate
Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.


