Safe Scripting Habits
Why safe scripting habits matter
Shell scripts are close to the operating system.
That is part of what makes them useful, but it also means mistakes can have real consequences:
- deleting the wrong files
- overwriting output
- running a command in the wrong directory
- using the wrong input file
- continuing after an important command failed
Safe scripting habits are not an advanced bonus. They are part of basic shell scripting.
Quote variable expansions
If you remember one safety habit early, make it this one.
Use:
cp "$source_file" "$target_file"
not:
cp $source_file $target_file
Why?
Because unquoted variables can split on spaces or expand in surprising ways.
If a variable contains:
My Notes.txt
then:
cat $file
can break into multiple pieces, while:
cat "$file"
preserves it as one path.
Check important input early
If your script needs arguments or files, validate them before doing real work.
Example:
#!/usr/bin/env bash
if [ -z "$1" ]; then
echo "Usage: $0 <file>"
exit 1
fi
if [ ! -f "$1" ]; then
echo "Error: file not found: $1"
exit 1
fi
This is safer than letting the script fail much later in a less obvious way.
Be explicit about directories
Many script mistakes are really location mistakes.
Bad pattern:
rm build/output.txt
This assumes the script is being run from the exact directory you expected.
Safer pattern:
cd ~/projects/my-app || exit 1
rm build/output.txt
or at least make the dependency on the directory obvious.
The point is not that every script must use cd. The point is that scripts should not quietly depend on context when they can be clearer.
Stop when important commands fail
If a command matters, do not ignore its failure.
Example:
cd "$project_dir" || exit 1
This means:
"If changing into this directory fails, stop the script immediately."
That is much safer than continuing in the wrong place.
This pattern is especially important after commands like:
cdcpmvmkdir- network or build commands your script depends on
Preview destructive work first
If a script deletes, moves, or overwrites files, preview the behavior when possible.
Example:
for file in *.tmp; do
echo "Would remove: $file"
done
Once that looks right, you can replace the preview with the real command:
for file in *.tmp; do
rm -- "$file"
done
This habit saves people from a lot of avoidable mistakes.
Treat rm, mv, and cp with respect
Commands that change files deserve extra care.
Useful habits:
- quote the paths
- preview in loops first
- avoid large destructive one-liners
- make target locations obvious
- do not assume a wildcard matched what you thought it matched
This is a good example of shell scripting style:
echo "Would copy $source_file to $target_dir"
before:
cp "$source_file" "$target_dir"
when the script is still being tested.
Use clear names
This is not shell-specific, but it matters a lot in scripts.
Prefer:
backup_dir="$HOME/backups"
log_file="cleanup.log"
over:
b="$HOME/backups"
l="cleanup.log"
Shell scripts are already dense enough. Clear names reduce risk.
Prefer readable scripts over clever one-liners
This:
for file in "$@"; do
cp -- "$file" "$backup_dir"
done
is usually better for a guide than a compressed line that does too much at once.
Small readable scripts are easier to:
- debug
- review
- trust
- modify later
Use logging and messages
A good script often says what it is doing.
Example:
echo "Creating backup in $backup_dir"
or:
echo "Error: missing input file"
These messages make scripts easier to follow and easier to debug.
Silent failure is frustrating. Clear messages are a safety feature.
Be careful with >
Remember:
command > file.txt
overwrites the file.
If the file should grow over time, use:
command >> file.txt
instead.
Any script that writes files should be explicit about whether replacement is intentional.
Prefer $(...) over backticks
This is partly a readability habit and partly a safety habit.
Prefer:
timestamp=$(date +%F)
over:
timestamp=`date +%F`
The $(...) form is easier to read and maintain.
A small safer script example
#!/usr/bin/env bash
backup_dir="$HOME/backups"
source_file="$1"
if [ -z "$source_file" ]; then
echo "Usage: $0 <file>"
exit 1
fi
if [ ! -f "$source_file" ]; then
echo "Error: file not found: $source_file"
exit 1
fi
mkdir -p "$backup_dir" || exit 1
cp "$source_file" "$backup_dir" || exit 1
echo "Backed up $source_file to $backup_dir"
Why this version is safer:
- it checks for missing input
- it checks that the file exists
- it quotes paths
- it stops if important commands fail
- it prints a clear result message
This is not fancy, but it is reliable beginner style.
Common mistakes
Assuming the script is in the right directory
Be careful when relative paths matter.
Acting on files before validating input
Check first. Change things second.
Letting the script continue after a critical failure
If a required step fails, stop and report it.
Testing with real data first
Use sample files when possible, especially for rename, move, or delete scripts.
Best practices
- Quote variable expansions
- Validate required input early
- Stop when important commands fail
- Preview destructive loops with
echo - Use clear variable names
- Print useful messages
- Favor readable multi-line scripts over clever compressed ones
Summary
- Safe shell scripting is about reducing surprises.
- Quote variables, validate input, and stop on important failures.
- Preview destructive work before doing it for real.
- Clear names and clear messages make scripts easier to trust.
- A simple careful script is better than a clever risky one.