Bash Notes
Modern Bash Cheat Sheet for Software Engineers
My notes on bash, initially generated by Claude, but will be expanded upon as I find more notable things to point out.
Basic Script Structure
#!/usr/bin/env bash
# Use this shebang - more portable than #!/bin/bash
set -euo pipefail # THE MOST IMPORTANT LINE
# -e: exit on error
# -u: exit on undefined variable
# -o pipefail: exit if any command in a pipe fails
Variables
# Define variables (no spaces around =)
name="value"
count=42
# Use variables (always quote them!)
echo "$name"
echo "${name}" # preferred - more explicit
# Command substitution (modern way)
current_date=$(date +%Y-%m-%d)
files=$(ls *.txt)
# Don't use backticks anymore: `date` (old style)
Conditionals
# Check if file exists
if [[ -f "myfile.txt" ]]; then
echo "File exists"
fi
# Check if directory exists
if [[ -d "mydir" ]]; then
echo "Directory exists"
fi
# Check if variable is empty
if [[ -z "$var" ]]; then
echo "Variable is empty"
fi
# Check if variable is NOT empty
if [[ -n "$var" ]]; then
echo "Variable has content"
fi
# String comparison
if [[ "$name" == "production" ]]; then
echo "Running in production"
fi
# Numeric comparison
if [[ $count -gt 10 ]]; then
echo "Count is greater than 10"
fi
# -eq (equal), -ne (not equal), -lt (less than), -le (less or equal), -gt, -ge
# Multiple conditions
if [[ -f "file.txt" && "$env" == "prod" ]]; then
echo "File exists and in prod"
fi
# Use [[ ]] not [ ] - it's more modern and safer
Loops
# Loop over files
for file in *.txt; do
echo "Processing $file"
done
# Loop over lines in a file
while IFS= read -r line; do
echo "$line"
done < input.txt
# Loop with counter
for i in {1..5}; do
echo "Iteration $i"
done
# Loop over array
files=("app.log" "error.log" "access.log")
for file in "${files[@]}"; do
echo "$file"
done
Functions
# Define a function
my_function() {
local input="$1" # first argument
local output="$2" # second argument
echo "Processing $input"
return 0 # return exit code (0 = success)
}
# Call a function
my_function "value1" "value2"
# Capture function output
result=$(my_function "test")
Exit Codes & Error Handling
# Check if last command succeeded
if command; then
echo "Success"
else
echo "Failed"
fi
# Check exit code
some_command
if [[ $? -eq 0 ]]; then
echo "Command succeeded"
fi
# Exit script with error
echo "Error occurred" >&2 # print to stderr
exit 1
# Run command but don't exit on failure
set +e
risky_command || echo "Command failed but continuing"
set -e
Input/Output & Pipes
# Redirect output to file (overwrites)
echo "text" > output.txt
# Append to file
echo "more text" >> output.txt
# Redirect stderr to stdout
command 2>&1
# Redirect stderr to file
command 2> error.log
# Redirect both stdout and stderr
command &> all_output.log
# Pipe commands together
cat file.txt | grep "error" | wc -l
# Better: use input redirection instead of cat
grep "error" < file.txt | wc -l
String Manipulation
text="hello world"
# Length of string
echo "${#text}"
# Substring (position:length)
echo "${text:0:5}" # "hello"
# Replace first occurrence
echo "${text/world/universe}"
# Replace all occurrences
echo "${text//o/0}" # "hell0 w0rld"
# Remove prefix
file="backup-2024-11-03.tar.gz"
echo "${file#backup-}" # "2024-11-03.tar.gz"
# Remove suffix
echo "${file%.tar.gz}" # "backup-2024-11-03"
# Uppercase/lowercase
echo "${text^^}" # "HELLO WORLD"
echo "${text,,}" # "hello world"
Common Patterns
# Check if script is run with sudo
if [[ $EUID -eq 0 ]]; then
echo "Running as root"
fi
# Get script directory (works even with symlinks)
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default value for variable
name="${1:-default_name}" # use $1 or "default_name" if empty
# Check if command exists
if command -v docker &> /dev/null; then
echo "Docker is installed"
fi
# Create temp file safely
tmpfile=$(mktemp)
trap "rm -f $tmpfile" EXIT # cleanup on exit
# Run commands in parallel and wait
long_task1 &
long_task2 &
wait # wait for all background jobs
Arrays
# Create array
servers=("web1" "web2" "db1")
# Add to array
servers+=("cache1")
# Access elements
echo "${servers[0]}" # first element
echo "${servers[@]}" # all elements
echo "${#servers[@]}" # array length
# Loop over array
for server in "${servers[@]}"; do
echo "$server"
done
Useful Commands for Daily Work
# Find files modified in last 24 hours
find . -mtime -1
# Find and delete
find . -name "*.tmp" -delete
# Search in files
grep -r "error" /var/log/
# Count lines/words/chars
wc -l file.txt
# Sort and remove duplicates
sort file.txt | uniq
# Show disk usage
du -sh /path/to/dir
# Show running processes
ps aux | grep java
# Monitor logs in real-time
tail -f /var/log/app.log
# Download file
curl -O https://example.com/file.zip
# Make HTTP request
curl -X POST -H "Content-Type: application/json" \
-d '{"key":"value"}' \
https://api.example.com/endpoint
# Extract tar.gz
tar -xzf archive.tar.gz
# Create tar.gz
tar -czf archive.tar.gz directory/
# Check if port is listening
netstat -tuln | grep 8080
# or on newer systems:
ss -tuln | grep 8080
Best Practices
- Always quote variables:
"$var"not$var - Use
set -euo pipefailat the top of every script - Use
[[ ]]not[ ]for conditions - Use
$( )not backticks for command substitution - Make scripts executable:
chmod +x script.sh - Check your scripts: Use shellcheck - itโs amazing
- Use
localfor function variables - Exit with meaningful codes: 0 = success, non-zero = failure
Common Pitfalls to Avoid
# BAD: Unquoted variable can break with spaces
for file in $files; do # DON'T DO THIS
# GOOD: Quoted variable
for file in "$files"; do
# BAD: Using [ ] instead of [[ ]]
if [ $var == "test" ]; then # can break if $var is empty
# GOOD: Using [[ ]] which handles empty vars
if [[ $var == "test" ]]; then
# BAD: Not checking if command exists
docker build .
# GOOD: Check first
if ! command -v docker &> /dev/null; then
echo "Docker not found" >&2
exit 1
fi
docker build .
Quick Reference Card
| Task | Command |
|---|---|
| Run script | bash script.sh or ./script.sh |
| Make executable | chmod +x script.sh |
| Exit on error | set -e |
| Debug mode | bash -x script.sh or set -x |
| Check syntax | bash -n script.sh |
| Get exit code | $? |
| Current directory | $(pwd) |
| Script location | ${BASH_SOURCE[0]} |
| All arguments | "$@" |
| Argument count | $# |
| Background job | command & |
| Wait for jobs | wait |