Bash Best Practices
A working script is a good start. A reliable script is better. Best practices are habits that make scripts easier to read, safer to run, and simpler to fix. Professional teams follow these habits in every shell project.
Script Quality Checklist
+--------------------------------------------------+ | A Reliable Script | | | | [x] Clear names [x] Quoted variables | | [x] Safety switches [x] Input checks | | [x] Helpful comments [x] Clean-up on exit | | [x] Small functions [x] Tested with | | ShellCheck | +--------------------------------------------------+
Start With a Solid Header
#!/usr/bin/env bash
# Name: backup.sh
# Purpose: Copy documents to the backup folder
# Usage: ./backup.sh source_folder
set -euo pipefailThe env form of the shebang finds Bash wherever it lives on the system. The comment lines tell the next reader what the file does.
Use the Safety Switches
The line set -euo pipefail combines three protections.
| Switch | Effect |
|---|---|
| -e | Stop the script when a command fails |
| -u | Stop when the script reads an undefined variable |
| -o pipefail | Report a failure from any step inside a pipe |
Without these switches, a script continues after errors and may delete or overwrite the wrong data.
Quote Every Variable
Unquoted variables split at spaces and expand wildcards. Double quotation marks prevent both problems.
# Risky
rm $file
# Safe
rm "$file"A file named my notes.txt breaks the risky line into two separate names.
Choose Clear Names
- Write variable names in lowercase with underscores, such as
backup_folder. - Write constants and environment variables in uppercase, such as
MAX_RETRY. - Pick descriptive names. The name
count_of_filesexplains more thanc.
Prefer Double Brackets for Tests
if [[ -f "$file" && -r "$file" ]]; then
echo "File is ready"
fiThe [[ ]] form handles empty values and pattern matching more safely than the single-bracket form.
Use Local Variables in Functions
Variables inside a function leak into the whole script by default. The local keyword keeps them private.
greet() {
local name="$1"
echo "Hello, $name"
}
greet "Asha"Check Input Before Using It
if [[ $# -ne 1 ]]; then
echo "Usage: $0 source_folder" >&2
exit 1
fi
if [[ ! -d "$1" ]]; then
echo "Error: $1 is not a folder" >&2
exit 1
fiThe symbol >&2 sends the message to the error stream, which keeps normal output clean.
Break Work Into Functions
Small functions with one job each make a script easy to test and read. Place a main function at the bottom:
#!/usr/bin/env bash
set -euo pipefail
check_input() {
[[ -d "$1" ]] || { echo "Folder missing" >&2; exit 1; }
}
make_backup() {
local stamp
stamp=$(date +%Y%m%d)
cp -r "$1" "$1_backup_$stamp"
echo "Backup created: $1_backup_$stamp"
}
main() {
check_input "$1"
make_backup "$1"
}
main "$@"Clean Up With a Trap
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXITThe trap removes the temporary file whether the script succeeds or fails.
Avoid Risky Patterns
- Skip parsing the output of
ls. Loop with wildcards such asfor f in *.txt. - Avoid hard-coded paths. Use variables or arguments.
- Skip
evalwith user input because it can run unwanted commands. - Double-check any
rmcommand that uses a variable. Test the variable for an empty value first. - Never store passwords inside a script file. Read them from protected environment variables or secure storage.
Add Meaningful Comments
Comments should explain the reason behind a step, not repeat the code.
# Weak comment
i=$((i+1)) # add one to i
# Strong comment
retry=$((retry+1)) # count failed uploads for the retry limitTest With ShellCheck
ShellCheck is a free tool that scans scripts and points out mistakes. Install it with sudo apt install shellcheck and run:
shellcheck backup.shIt reports unquoted variables, wrong syntax, and risky patterns with line numbers and short explanations.
Debug With Trace Mode
bash -x backup.shThe -x flag prints every command before it runs, which reveals where a script goes wrong.
Key Takeaways
- Start every script with a shebang, a short description, and
set -euo pipefail. - Quote variables to protect values that contain spaces.
- Name variables clearly and keep function variables local.
- Validate input before any action.
- Use traps for cleanup and functions for structure.
- Run ShellCheck on every script before sharing it.
