Skip to content

Best_practices

This chapter covers the best practices for writing production-quality bash scripts. Following these practices ensures your scripts are reliable, maintainable, and secure.


#!/usr/bin/env bash
#
# Script description
#
# Usage: script.sh [OPTIONS]
#
# Options:
# -h, --help Show this help message
# -v, --verbose Enable verbose output
# -f, --file Input file
#
# Author: Your Name
# Version: 1.0.0
#
set -euo pipefail
# Global variables
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_VERSION="1.0.0"
# Usage function
usage() {
sed -n '3,15p' "$0"
}
# Logging
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }
# Cleanup
cleanup() {
# Add cleanup code
true
}
trap cleanup EXIT
# Main function
main() {
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-f|--file)
FILE="$2"
shift 2
;;
*)
error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Script logic
log "Starting $SCRIPT_NAME"
}
main "$@"

Terminal window
# Good
readonly MAX_RETRIES=3
readonly CONFIG_FILE="/etc/app.conf"
# Avoid
MAX_RETRIES=3 # Can be modified
Terminal window
# Constants
readonly MAX_CONNECTIONS=100
readonly DEFAULT_PORT=8080
# Variables (lowercase)
local count=0
local filename=""
Terminal window
# Good
echo "Hello $name"
rm -rf "$directory"
# Avoid
echo "Hello $name" # Unquoted
rm -rf $directory # Dangerous!

Terminal window
# Good
my_function() {
local result=""
local -i count=0
# function code
}
# Avoid global variables in functions
my_function() {
result="" # Creates global variable
}
Terminal window
# Good - return exit status
check_file() {
if [[ -f "$1" ]]; then
return 0
else
return 1
fi
}
# Not for returning data
# Use echo or assign to variable

#!/usr/bin/env bash
set -euo pipefail
Terminal window
# Good
if ! command; then
echo "Command failed"
exit 1
fi
# Or using &&
command && echo "Success"
# Or using ||
command || { echo "Failed"; exit 1; }
#!/bin/bash
cleanup() {
# Remove temp files
# Close file descriptors
# Kill child processes
}
trap cleanup EXIT
trap 'error "Interrupted"; exit 130' INT TERM

#!/bin/bash
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <arg1> <arg2>"
exit 1
fi
Terminal window
: "${DATABASE_URL:?Environment variable DATABASE_URL is required}"
: "${API_KEY:?Environment variable API_KEY is required}"
Terminal window
if [[ ! "$port" =~ ^[0-9]+$ ]]; then
echo "Error: Port must be a number"
exit 1
fi
if [[ "$port" -lt 1 ]] || [[ "$port" -gt 65535 ]]; then
echo "Error: Port must be between 1 and 65535"
exit 1
fi

Terminal window
# Bad
PASSWORD="secret123"
# Good - use environment variables
: "${DB_PASSWORD:?DB_PASSWORD must be set}"
Terminal window
# Good
rm -- "$file"
command -- "$arg"
# Avoid ambiguous arguments
rm -f "$file" # Could be confused with filename
Terminal window
# Prevent path traversal
if [[ "$filename" == *".."* ]]; then
echo "Invalid filename"
exit 1
fi
# Use absolute paths when needed
filepath="$(realpath "$filepath")"

Terminal window
# Bad - subshell in loop
for item in items; do
result=$(process "$item")
done
# Better - process in loop
for item in items; do
process "$item"
done
Terminal window
# Good - built-in
[[ $var == "value" ]]
# Slower - external command
test "$var" = "value"
# Good - built-in string operations
echo "${var##*/}" # Basename
echo "${var%/*}" # Dirname
Terminal window
# Good
files=( *.txt )
for file in "${files[@]}"; do
process "$file"
done
# Avoid parsing command output in loops
for file in $(ls *.txt); do # Word splitting issues!
process "$file"
done

Terminal window
# Function description
# Processes the input file and generates a report
process_file() {
# Local variables
local input_file="$1"
# Check if file exists
if [[ ! -f "$input_file" ]]; then
return 1
fi
# Process the file
# ...
}
Terminal window
# Good
readonly MAX_RETRY_ATTEMPTS=3
local backup_filename=""
# Avoid
readonly M=3
local b=""

Terminal window
# Test missing arguments
./script.sh
# Should show usage
# Test invalid input
./script.sh -p invalid
# Should show error
# Test with required env vars
DATABASE_URL="" ./script.sh
# Should fail

In this chapter, you learned:

  • ✅ Script structure and templates
  • ✅ Variable best practices
  • ✅ Function best practices
  • ✅ Error handling best practices
  • ✅ Input validation
  • ✅ Security best practices
  • ✅ Performance best practices
  • ✅ Documentation
  • ✅ Testing

Continue to the next chapter to learn about Common One-liners.


Previous Chapter: Strict Mode Next Chapter: Common One-liners