Table of Contents
- Fundamental Concepts
- Usage Methods
- Common Practices
- Best Practices
- Advanced Topics
- Conclusion
- References
Fundamental Concepts
Variables and Data Types
Bash variables store data for reuse in scripts. They are weakly typed (no explicit type declaration) and typically hold strings or numbers.
Declaring Variables
Variables are declared without a type, using = (no spaces around =):
name="Alice"
age=30
Accessing Variables
Use $ to access a variable’s value. Enclose in quotes to preserve spaces and avoid word splitting:
echo "Name: $name" # Output: Name: Alice
echo "Age: ${age}" # Braces optional here, but required for complex expressions
echo "Next year: $((age + 1))" # Arithmetic expansion: Output: Next year: 31
Environment vs. Local Variables
- Local variables: Visible only in the current shell/script.
- Environment variables: Exported to child processes with
export:export PATH="$PATH:/usr/local/bin" # Make new path available to subshells
Control Structures
Control structures like conditionals and loops enable dynamic script behavior.
Conditionals (if-else)
Check conditions using if, elif, and else. Use [ ] (POSIX test) or [[ ]] (Bash-specific, supports regex and globbing):
file="data.txt"
# Check if file exists and is readable
if [[ -r "$file" ]]; then
echo "$file is readable."
elif [[ -f "$file" ]]; then
echo "$file exists but is not readable."
else
echo "$file does not exist."
fi
Common condition flags:
-f: File exists and is a regular file-d: Directory exists-r: Readable-w: Writable-x: Executable$a -eq $b: Arithmetic equality
Loops
for Loops: Iterate over lists (files, arguments, etc.):
# Loop over files in the current directory
for file in *; do
echo "Processing: $file"
done
# Loop with a counter (Bash 3+)
for ((i=1; i<=5; i++)); do
echo "Count: $i"
done
while Loops: Run until a condition fails:
count=1
while [[ $count -le 3 ]]; do
echo "Loop $count"
((count++)) # Increment counter
done
until Loops: Run until a condition succeeds (opposite of while):
count=5
until [[ $count -eq 0 ]]; do
echo "Countdown: $count"
((count--))
done
Command Substitution
Capture the output of a command into a variable using $(command) (preferred) or backticks `command`:
current_date=$(date +"%Y-%m-%d") # Store output of `date`
echo "Today: $current_date" # Output: Today: 2024-05-20
file_count=$(ls | wc -l) # Count files in directory
echo "Files: $file_count"
Usage Methods
Shebang Line
The shebang (#!) at the start of a script specifies the interpreter. Always use #!/bin/bash for Bash-specific scripts (not #!/bin/sh, which may point to a minimal POSIX shell):
#!/bin/bash
# This script requires Bash features (e.g., arrays, [[ ]])
Script Execution
To run a script:
- Make it executable with
chmod +x script.sh. - Execute with
./script.sh(or absolute path/path/to/script.sh).
Alternatively, run directly with Bash: bash script.sh (bypasses the shebang but ensures Bash is used).
Command-Line Arguments
Scripts accept arguments via positional parameters:
| Parameter | Description |
|---|---|
$0 | Script name |
$1, $2 | First, second argument, etc. |
$@ | All arguments (as a list) |
$# | Number of arguments |
$? | Exit code of the last command |
Example script (greet.sh):
#!/bin/bash
echo "Script name: $0"
echo "Hello, $1! You provided $# arguments."
echo "All args: $@"
Run with:
./greet.sh "Bob" # Output: Script name: ./greet.sh; Hello, Bob! You provided 1 arguments. All args: Bob
Flags with getopts: For complex argument parsing (e.g., -v for verbose), use getopts:
while getopts "v:" opt; do
case $opt in
v) echo "Verbose mode: $OPTARG" ;; # -v followed by a value
\?) echo "Invalid option: -$OPTARG" ;;
esac
done
Common Practices
Functions
Functions encapsulate reusable logic. Define them with function name { ... } or name() { ... }:
greet() {
local name="$1" # Local variable (scoped to the function)
echo "Hello, $name!"
return 0 # Exit code (optional; defaults to last command's exit code)
}
greet "Charlie" # Output: Hello, Charlie!
Return Values: Use return for exit codes (0-255) or echo to return strings (capture with $(greet "Dave")).
Error Handling
Prevent silent failures with these techniques:
set -e: Exit immediately if any command fails.set -u: Treat unset variables as errors.set -o pipefail: Make pipelines fail if any command in the pipeline fails.
Add to the top of scripts:
#!/bin/bash
set -euo pipefail # "Strict mode"
trap for Cleanup: Run commands on script exit (e.g., delete temp files):
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT # Delete temp_file when script exits
Logging
Use echo or logger (for system logs) to track script progress:
log() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] $1"
}
log "Starting backup..." # Output: [2024-05-20 14:30:00] Starting backup...
Best Practices
Script Structure
Organize scripts for readability:
- Shebang (
#!/bin/bash) - Comments: Explain why, not what (e.g.,
# Retry 3x to handle network flakiness). - Variables: Define constants and configuration at the top.
- Functions: Group related logic.
- Main Logic: Call functions and orchestrate workflow.
Example template:
#!/bin/bash
set -euo pipefail
# Configuration
BACKUP_DIR="/backups"
MAX_RETRIES=3
# Functions
log() { ... }
backup_file() { ... }
# Main
log "Starting backup..."
backup_file "data.txt"
log "Backup complete."
Security
-
Quote Variables: Use
"$var"to prevent word splitting and injection attacks:# UNSAFE: If $file has spaces, `rm $file` will fail # SAFE: `rm "$file"` handles spaces correctly -
Avoid
eval:evalexecutes arbitrary code, posing security risks. -
Validate Input: Check arguments before use:
if [[ -z "$1" ]]; then echo "Error: Argument required." >&2 # Redirect to stderr exit 1 fi -
Limit Permissions: Run scripts with the least privilege needed.
Performance Optimization
- Use Builtins: Prefer Bash builtins (e.g.,
[[ ]]over[ ],(( ))overexpr) for speed. - Avoid Subshells: Subshells (
$(command),(...)) add overhead. Use{ ... }for grouping when possible. - Efficient Loops: Process files in bulk with
find -execorxargsinstead of looping line-by-line.
Advanced Topics
Arrays and Associative Arrays
Arrays store ordered lists:
fruits=("apple" "banana" "cherry")
echo "First fruit: ${fruits[0]}" # Output: apple
echo "All fruits: ${fruits[@]}" # Output: apple banana cherry
# Loop over array
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
Associative Arrays (Bash 4+) store key-value pairs:
declare -A user # Declare associative array
user["name"]="Diana"
user["age"]=28
echo "User: ${user["name"]}, Age: ${user["age"]}" # Output: User: Diana, Age: 28
Process Substitution
Treat command output as a temporary file with <(command):
# Compare sorted outputs of two commands
comm <(sort file1.txt) <(sort file2.txt)
Debugging Techniques
set -x: Print commands as they run (addset -xto the script or runbash -x script.sh).trap DEBUG: Log each command before execution:trap 'echo "Running: $BASH_COMMAND"' DEBUG
Conclusion
Bash scripting is a powerful tool for automating tasks, managing systems, and building workflows. By mastering fundamentals like variables, loops, and functions, adopting best practices for security and readability, and leveraging advanced features like arrays and process substitution, you can write robust, efficient scripts.
Remember: The best scripts are simple, well-documented, and resilient to edge cases. Continuously refine your skills by experimenting with real-world problems—whether it’s automating backups, parsing logs, or deploying applications.
References
- GNU Bash Manual
- Bash Hackers Wiki
- ShellCheck: Linter for Bash scripts
- Advanced Bash-Scripting Guide
- Bash FAQ