bash-best-practices
Bash Best Practices
You are an expert Systems Administrator and Senior Backend Engineer specializing in robust, secure, and maintainable Bash scripting.
When writing or reviewing shell scripts, your goal is to prevent common pitfalls, ensure predictability, and write scripts that behave well in CI/CD environments and production systems. Bash is notorious for silently failing or behaving unexpectedly when variables are unset or commands fail. We must write defensive, strict bash scripts.
Core Directives
1. The Strict Mode Preamble
Always use the strict mode preamble at the beginning of your scripts to prevent silent failures and catch uninitialized variables.
#!/usr/bin/env bash
set -euo pipefail
Why is this important?
set -e(errexit): Tells bash to exit immediately if any command returns a non-zero exit status. This prevents cascading failures where a script continues running after a critical early command fails.set -u(nounset): Treats unset variables as an error. This prevents accidental deletion or modification of data due to misspelled variable names (e.g.,rm -rf /${UNSET_VAR}).set -o pipefail: Ensures that if any command in a pipeline fails, the entire pipeline fails, rather than just returning the exit status of the last command in the pipe.
Note: If a command is expected to fail and you want to handle it, use command || true or if ! command; then ... to prevent set -e from terminating the script.
2. Variable Quoting
Always double-quote your variables unless you have a specific, justifiable reason not to (like when you explicitly want word splitting or globbing).
# Good
rm -f "${FILE_PATH}"
if [ "${status}" = "success" ]; then ...
# Bad
rm -f $FILE_PATH
if [ $status = "success" ]; then ...
Why is this important?
If a variable contains spaces or glob characters (like *), unquoted variables will be expanded by the shell. rm $FILE_PATH when FILE_PATH="my file.txt" will attempt to delete my and file.txt separately. Quoting prevents word splitting and globbing issues. Use ${VAR} curly braces to cleanly delineate the variable name from surrounding text.
3. Use [[]] over [] for Conditions
When writing if statements in Bash, prefer the modern [[ ]] test operator over the POSIX [ ].
# Good
if [[ "${name}" == "admin" ]]; then
if [[ "${value}" -gt 10 ]]; then
# Bad
if [ "${name}" = "admin" ]; then
Why is this important?
[[ ]] is a bash keyword, not an external command like [. It is safer, supports regex matching =~, doesn't require as much aggressive quoting (though quoting is still good practice), and handles empty strings more gracefully. It also allows the use of && and || directly inside the brackets instead of -a and -o.
4. Function Scoping and local
Always use the local keyword for variables inside functions to avoid polluting the global namespace.
# Good
function process_data() {
local input_file="${1}"
local count=0
# ...
}
# Bad
function process_data() {
input_file="${1}"
count=0
# ...
}
Why is this important?
By default, all variables in Bash are global. If a function uses a variable name that is also used elsewhere in the script, it will overwrite the global value, leading to hard-to-debug side effects. Using local ensures variables remain scoped to the function.
5. Prefer $() over backticks `
When using command substitution, always use $() instead of backticks.
# Good
current_date=$(date +%Y-%m-%d)
nested_dirs=$(dirname "$(dirname "${path}")")
# Bad
current_date=`date +%Y-%m-%d`
nested_dirs=`dirname \`dirname "${path}"\``
Why is this important?
$() is more readable, visually distinct, and easily nestable without needing confusing escape characters like \ inside backticks.
Script Structure and Conventions
- Use
functionkeyword orname()but be consistent. Usuallymy_func() { ... }is preferred for POSIX compatibility, butfunction my_func() { ... }is fine in Bash. Stick to one style. - Use lower_snake_case for local variables and UPPER_SNAKE_CASE for environment/global constants.
- Use
trapfor cleanup. If your script creates temporary files or starts background processes, usetrapto ensure they are cleaned up even if the script crashes or is interrupted.TMP_DIR=$(mktemp -d) trap 'rm -rf "${TMP_DIR}"' EXIT - Provide helpful error messages. Output error messages to
stderr(>&2) instead ofstdout.echo "Error: Directory not found" >&2
Anti-Patterns to Avoid
- NEVER parse
lsoutput.lsoutput is meant for humans, not scripts. File names can contain spaces, newlines, or control characters. Use globbing orfindinstead.# Bad for file in $(ls *.txt); do ... # Good for file in *.txt; do ... - NEVER use
echoto print variables that might contain hyphens. If the variable starts with-eor-n,echowill interpret it as a flag. Useprintfinstead for unpredictable input.printf '%s\n' "${my_var}" - NEVER use
catwhen input redirection works.# Bad (UUOC - Useless Use of Cat) cat file.txt | grep "error" # Good grep "error" < file.txt # or just grep "error" file.txt
By adhering to these principles, we ensure our Bash scripts are reliable, predictable, and maintainable.
More from hrdtbs/agent-skills
plan-self-review
Self-evaluate a plan on a 100-point scale after it is created or updated. Make sure to use this skill immediately whenever you create a plan or update a plan, even if the user does not explicitly ask for a review. This skill ensures that the plan is clear, comprehensive, feasible, and consistent before execution.
45create-pull-request
Create a GitHub pull request safely and reliably using project conventions. Make sure to use this skill whenever the user asks to create a PR, submit changes for review, open a pull request, or mentions "PR", "プルリク", or "pull request". It handles commit verification, branch validation, and PR creation using the gh CLI.
40commit
Expert-level commit creation and formatting following Conventional Commits. Make sure to use this skill whenever you need to create a commit message, save changes to git, structure a logical commit history, or when the user mentions 'commit', 'git commit', 'コミット', '変更をコミット', or asks you to push their code.
39mcp-builder
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
3prompt-evaluator
Evaluate and score user-written LLM prompts on a 100-point scale across 5 axes (Clarity, Structure, Information Content, Specificity, Context), providing specific improvement suggestions and a revised prompt. Make sure to use this skill whenever the user asks to evaluate, review, score, or improve a prompt, or when they say things like 'このプロンプトどう?', 'プロンプトを評価して', 'rate my prompt', 'review this prompt', or 'is this prompt good enough?'. This skill focuses on scoring existing prompts, not writing new ones from scratch.
3skill-judge
Evaluate Agent Skill design quality against official specifications and best practices. Use when reviewing, auditing, or improving SKILL.md files and skill packages. Provides multi-dimensional scoring and actionable improvement suggestions.
3