Bash Learning Materials
Must Read: The Bash Manual
The official Bash manual is non-negotiable. It’s comprehensive and dense, but reading it thoroughly before building production scripts will save you hours of debugging.
Access it locally with:
man bash
Or online at the GNU Bash Manual. Start with the INVOCATION section to understand shell behavior, then work through Parameter Expansion and Command Substitution—these are where most bugs live.
Key sections to prioritize:
- Quoting: Understanding when to quote and when not to
- Parameter Expansion: Variable manipulation, defaults, and substring removal
- Command Substitution:
$(...)vs backticks (use$()) - Arithmetic Expansion:
$((...))for math operations - Conditional Constructs:
if,case,[[...]]vs[...]
Style Guides and Best Practices
Google Shell Style Guide
Google’s Shell Style Guide is explicitly a Bash guide and worth reading entirely. It establishes conventions that reduce bugs:
- Use
set -euo pipefailat the top of scripts to catch errors early - Prefer
[[...]]over[...]for conditionals - Quote variables:
"$var"not$var - Use
localin functions to avoid polluting the global namespace - Comment non-obvious logic
Adapt it to your team’s needs rather than following it dogmatically. Some rules make sense for large organizations but not for small tools.
Defensive scripting template
A solid starting point for any production script:
#!/bin/bash
set -euo pipefail
# Optional: more verbose error messages
trap 'echo "Error on line $LINENO"' ERR
# Your script code here
Breaking this down:
-e: Exit on any error-u: Fail on undefined variables-o pipefail: Propagate errors through pipestrap: Catches errors for cleanup or debugging
Intermediate: ShellCheck
Before moving to advanced topics, integrate ShellCheck into your workflow. It’s a static analysis tool that catches common mistakes:
shellcheck myscript.sh
Most distributions package it. Use it as a linter in your editor or CI pipeline. It catches unquoted variables, unused variables, and logic errors that take hours to debug otherwise.
Check the online version for explanations of each warning code. Enable it in your editor:
- Vim/Neovim: Use
aleornvim-lint - VS Code: Install the ShellCheck extension
- Emacs: Use
flycheckwith the ShellCheck checker
Advanced: Advanced Bash-Scripting Guide
Once comfortable with the basics, Mendel Cooper’s Advanced Bash-Scripting Guide covers deeper patterns and techniques. Read selectively—focus on sections relevant to your work:
- Process substitution:
<(...)and>(...)for reading command output as files - Arrays and associative arrays: Proper handling of collections
- Regular expressions and pattern matching:
=~operator,[[ ]]matching - Subshells and command grouping: Understanding scope and performance implications
The guide is verbose but thorough. The online version is kept current; avoid outdated PDF copies.
Debugging Bash Scripts
Use these techniques when scripts fail:
- Enable debug output:
bash -x script.shorset -xwithin the script - Trace individual commands:
set -vprints commands as they’re read - Format debug output:
PS4='+ ${BASH_SOURCE}:${LINENO} 'for better tracing - Check exit codes: Each command sets
$?; test withif command; then
Example debug session:
#!/bin/bash
set -euo pipefail
# Enable debug mode for a function
debug_function() {
(set -x; some_command "$@")
}
For complex debugging, the bash -x output can be overwhelming. Redirect to a file and review selectively:
bash -x script.sh 2> debug.log
Common Pitfalls to Study
- Word splitting:
$varsplits on whitespace; use"$var"or arrays - Globbing in conditionals:
[[ $var == *.txt ]]is safe;[ $var == *.txt ]expands globs - Subshell scope: Variables set in
(...)don’t persist; use{ ... ; }instead - Unset variables:
set -ufails on undefined vars; catch this early - Array handling: Use proper syntax:
"${array[@]}"not$array - Pipe subshells: Variables assigned in pipes (rightmost command) don’t affect the parent shell; use process substitution or write to temp files
Process Substitution in Practice
When you need to pipe multiple inputs or capture outputs:
# Read from multiple sources simultaneously
diff <(sort file1.txt) <(sort file2.txt)
# Write to multiple outputs
tee >(gzip > file.gz) >(bzip2 > file.bz2) < input.txt
This is more efficient than creating temporary files and avoids subshell variable scope issues.
Modern Alternatives for Complex Tasks
For scripts exceeding a few hundred lines, consider Python 3 with libraries like subprocess or dedicated tools like Ansible. Bash excels at piping commands and system automation but becomes hard to maintain, test, and debug as complexity grows.
Use Bash when:
- Gluing existing Unix tools together
- Writing system startup or deployment scripts
- Quick automation tasks under 100 lines
Switch to Python or Go when:
- Complex logic, data structures, or error handling
- Testing requirements are significant
- Team standardization on a compiled or statically-typed language
Practical Next Steps
- Set up ShellCheck in your editor and fix warnings
- Read the Bash manual sections on quoting and expansions
- Apply the Google style guide’s essential rules (
set -euo pipefail, quoting,[[...]]) - Build small scripts, test them, and review with ShellCheck
- Write a helper library with common functions (logging, error handling, argument parsing)
- Graduate to the Advanced Bash-Scripting Guide once you’ve internalized the fundamentals
The investment in learning Bash properly pays off—you’ll write more reliable automation and debug faster when things break.