Shell & Scripting Standards (Bash / PowerShell)
Scripts run our builds, deploys, automation, and one-off ops tasks. A careless script can delete data, leak a secret, or stop halfway. Treat scripts like real code: fail fast, quote everything, handle errors, keep secrets out, and make them safe to run again. This applies to Bash and PowerShell.
Scripts are written quickly and trusted widely. That is why they are risky. A script that does not stop on error can keep running after a failed step and leave a mess. An unquoted variable can turn into a destructive command. A secret printed to a log is a leak. These rules make scripts predictable and safe. Anything important a script does belongs in the pipeline, reviewed like other code (see CI/CD & Deployment).
Prefer well-tested tools over long custom scripts. When you do write a script, make it robust.
Fail fast & predictably
- AlwaysMake scripts fail fast: Bash
set -euo pipefail; PowerShell$ErrorActionPreference = 'Stop'. A failed step then stops the script instead of running on in a broken state. - DoCheck return codes and handle errors for commands that can fail. Exit with a non-zero code on failure so callers and CI know it failed.
- DoQuote all variable expansions (Bash
"$var") to avoid word-splitting and glob problems. In PowerShell, be careful about types and arrays. - DoValidate inputs, arguments, and required environment first. Show a clear message if something is missing.
Safety with side effects
- DoMake scripts idempotent and safe to run again where possible (check-before-create,
mkdir -p). A retry should not apply twice or break. - DoTake great care with destructive operations (delete, overwrite, reset). Confirm the target, run a dry-run or preview first, and never assume a variable is set before using it in a path.
- DoUse absolute paths or script-relative paths on purpose. Do not depend on the caller's working directory.
- NeverRun a destructive command with an unset or unvalidated variable in the path. The classic case is
rm -rf "$DIR/"with an empty$DIR.
Secrets & security
- AlwaysRead secrets from the vault or secure environment at runtime. Never hard-code them in scripts and never print or log them (see Secrets at Rest & in Transit).
- DoCommand lines and verbose or trace output can leak secrets through process lists and CI logs. Pass secrets through the environment or stdin, not as visible arguments, and turn off tracing around them.
- DoQuote and validate any external input used in a command to avoid injection. This is the shell version of SQL injection (see Trust Boundaries).
- NeverCommit a secret in a script, or pipe a remote script straight into a shell (
curl ... | bash) from a source you do not trust (see Dependency & Supply-Chain Security).
Readability & maintenance
- DoAdd a short header comment that says what the script does and how to use it. Name variables clearly, and split larger scripts into functions (see Coding Standards & Style).
- DoLog what the script is doing at key steps so failures are easy to diagnose. Lint scripts with ShellCheck for Bash and PSScriptAnalyzer for PowerShell.
- DoKeep scripts in source control and review them. Put anything that touches production through the pipeline (see Version Control Hygiene, CI/CD & Deployment).
- AvoidLarge, undocumented scripts that only the author understands. Prefer clear, tested tools for anything important.
A safe script, end to end
#!/bin/bash
API_KEY=sk_live_123 # hard-coded secret
rm -rf $BUILD_DIR/* # unquoted and maybe unset: disaster
deploy --key $API_KEY # key visible in process list and logs
There is no set -euo pipefail, so errors are ignored. It has a committed live secret. It runs rm -rf on an unquoted, possibly empty path, which can delete the wrong thing. And the key is shown on the command line. Several serious problems.
#!/bin/bash
set -euo pipefail
: "${BUILD_DIR:?BUILD_DIR is required}" # fail if unset
API_KEY="$(get-secret deploy-key)" # from vault, not echoed
rm -rf "${BUILD_DIR:?}"/*
deploy --key-env API_KEY # passed via env, not argv
This fails fast, refuses to run with an unset path, reads the secret from the vault without printing it, and quotes everything. It is safe to run and safe to run again.
Self-review checklist
- AskDoes it fail fast and exit non-zero on error, with inputs validated?
- AskAre all variables quoted, and is every destructive command guarded against unset/empty values?
- AskAre secrets sourced securely and kept out of logs, args, and source control?
- AskIs it idempotent, documented, linted, and reviewed?