Great Stack to Doesn't Work Bonus: 10 Bash Scripting Golden Rules
Great Stack to Doesn't Work β Bonus 10 Bash Scripting Golden Rules Because your deployment script is production code whether you admit it or not. 1. Start every script with set -euo pipefail. #!/usr/bin/env bash set -euo pipefail -e: Exit on any command failure. Without it, a failed rm

Great Stack to Doesn't Work β Bonus 10 Bash Scripting Golden Rules Because your deployment script is production code whether you admit it or not. 1. Start every script with set -euo pipefail. #!/usr/bin/env bash set -euo pipefail -e: Exit on any command failure. Without it, a failed rm or cp is silently ignored and the script continues with corrupted state. -u: Treat undefined variables as errors. $UNSET_VAR expands to empty string by default. With -u, it's a hard error. This catches typos ($DATABSE_URL instead of $DATABASE_URL) before they reach production. -o pipefail: A pipeline fails if any command in it fails. Without it, bad_command | grep something returns grep's exit code, hiding bad_command's failure. 2. Quote your variables. Always. # BAD: breaks if filename has spaces rm $file # GOOD: works with any filename rm "$file" # BAD: word splitting nightmare for f in $files; do # GOOD: preserves entries with spaces for f in "${files[@]}"; do Unquoted variables undergo word splitting and glob expansion. A filename with spaces becomes two arguments. A variable containing * expands to every file in the directory. 3. Never use eval. eval takes a string and executes it as a command. It's the rm -rf / of bash programming β it works until someone puts something unexpected in that string. # DANGEROUS: if $user_input contains "; rm -rf /" eval "echo $user_input" # SAFE: use arrays for dynamic commands cmd=("docker" "run" "--rm" "$image") "${cmd[@]}" If you think you need eval, you almost certainly need an array instead. 4. Use ShellCheck. Non-negotiable. ShellCheck catches quoting errors, undefined variables, deprecated syntax, and common pitfalls statically. Run it in CI. shellcheck myscript.sh It finds bugs you'd never catch in code review. Enable it as a pre-commit hook and you'll wonder how you lived without it. 5. Clean up with trap. Temporary files, background processes, lock files β if your script creates them, it must clean them up, even on failure. cleanup() { rm -f "$TEMP_FILE" kill "$BG_PID" 2>/dev/null || true } trap cleanup EXIT TEMP_FILE=$(mktemp) some_command > "$TEMP_FILE" & BG_PID=$! trap ... EXIT fires on normal exit, error exit, and most signals. No more orphaned temp files. 6. Use process substitution instead of temp files. # OLD: write to temp, read from temp command1 > /tmp/result.txt command2 < /tmp/result.txt # BETTER: no temp file needed command2 < <(command1) # COMPARE TWO COMMANDS: diff <(sort file1) <(sort file2) <(command) creates a virtual file descriptor. No temp files to clean up. No race conditions. 7. Use parameter expansion instead of external commands. # SLOW: spawns a subprocess filename=$(basename "$path") extension=$(echo "$file" | sed 's/.*\.//') # FAST: pure bash filename="${path##*/}" extension="${file##*.}" dirname="${path%/*}" without_ext="${file%.*}" # Default values db_host="${DB_HOST:-localhost}" db_port="${DB_PORT:-5432}" Each $(...) forks a subprocess. In a loop processing 10,000 items, the subprocess overhead dominates. Parameter expansion is instant. 8. Use arrays properly. # WRONG: space-delimited string files="file one.txt file two.txt" # RIGHT: proper array files=("file one.txt" "file two.txt") # Iterate safely for f in "${files[@]}"; do echo "Processing: $f" done # Pass as arguments command "${files[@]}" # Append files+=("file three.txt") # Length echo "${#files[@]}" Arrays preserve elements with spaces, newlines, and special characters. Strings don't. 9. Use here-docs for multi-line strings. # HERE-DOC: variables expanded cat << EOF Hello $USER, Today is $(date). Your home is $HOME. EOF # HERE-DOC with quotes: no expansion (literal) cat << 'EOF' This $variable is not expanded. Neither is $(this command). EOF # HERE-STRING: one-liner grep "pattern" <<< "$variable" Here-docs are cleaner than escaped multi-line echo statements and more readable than concatenated strings. 10. Test with Bats. Bats (Bash Automated Testing System) is a testing framework for bash scripts. # test_deploy.bats @test "deployment script requires ENVIRONMENT variable" { unset ENVIRONMENT run ./deploy.sh [ "$status" -eq 1 ] [[ "$output" == *"ENVIRONMENT is required"* ]] } @test "deployment script validates environment name" { ENVIRONMENT="invalid" run ./deploy.sh [ "$status" -eq 1 ] [[ "$output" == *"must be staging or production"* ]] } If your bash script is important enough to run in production, it's important enough to test. Bats makes it simple. Which bash scripting mistake has bitten you the hardest? Do you test your bash scripts β and if so, how? If you enjoyed this, I write about production engineering, AI systems, and the messy reality of building software at scale. Follow me: LinkedIn β Mehmet TURAΓ X/Twitter β @TuracTheThinker This is part of the **Great Stack to Doesn't Work* series β a survival guide for when everything goes wrong in production. Follow the series to catch every episode.*
Key Takeaways
- β’Great Stack to Doesn't Work β Bonus 10 Bash Scripting Golden Rules Because your deployment script is production code whether you admit it or not. 1
- β’This story was reported by Dev.to, covering developments in the dev space.
- β’AI advancements continue to reshape industries β read the full article on Dev.to for complete coverage.
π Continue reading the full article:
Read Full Article on Dev.to βShare this article



