Bash is the default shell on nearly every Linux distribution and macOS. It powers deployment pipelines, system maintenance scripts, development workflows, and automated testing. Every developer who works with servers, containers, or CI/CD systems eventually needs to write or read a shell script. Yet bash syntax is famously tricky — the difference between $var and "$var" can introduce spaces-in-filenames bugs that break production deployments. The rules for parameter expansion, the behavior of unquoted variables, and the subtle differences between [ ] and [[ ]] catch even experienced developers off guard.
Our free interactive bash scripting cheat sheet provides eighty-plus copyable commands and syntax examples organized into twelve categories: variables and parameters, conditionals, loops, functions, string manipulation, arrays, redirection and pipes, file test operators, arithmetic operations, exit codes and error handling, and useful one-liners. The Blacksmith's Forge aesthetic — deep charcoal background with molten amber accents, floating ember particles, and forged-iron typography — transforms script reference into craft. Everything runs in your browser. No server, no signup, no data collection.
Why Bash Scripting Still Matters
Despite the rise of Python, Go, and JavaScript for automation, bash remains the universal glue of Unix-like systems. It is the only language guaranteed to exist on every Linux server, every Docker container, and every CI/CD runner. A twenty-line bash script can install dependencies, configure services, rotate logs, and send alerts — tasks that would require hundreds of lines and multiple dependencies in most other languages.
Bash scripting is not going away. Kubernetes init containers run shell commands. GitHub Actions workflows execute bash scripts. Docker ENTRYPOINT scripts bootstrap containers. Cloud-init scripts configure new VM instances. The ability to read and write bash is a fundamental skill for anyone who touches a Unix-like system.
Yet bash is also a minefield of footguns. Word splitting, globbing, and exit code handling behave in ways that surprise programmers coming from other languages. A well-organized cheat sheet provides three benefits: it surfaces the correct syntax for common patterns, it warns about dangerous defaults, and it provides copyable snippets that follow best practices.
Variables and Parameters
Bash variables are strings by default. No type declarations are required, but this flexibility comes with pitfalls — unquoted variables undergo word splitting and pathname expansion, which is the root cause of countless script bugs.
Variable Assignment
Assignment uses = with no spaces around it. Quoting the right-hand side prevents word splitting but is not always necessary for simple values.
name="John Doe"
age=30
readonly PI=3.14159 # cannot be changed
declare -r MAX=100 # same as readonly Variable Reference
Use $var or ${var} to access values. The braces form is safer because it disambiguates variable names from adjacent text. Always quote variables when passing them as arguments to avoid word splitting.
echo "Hello, $name"
echo "Hello, ${name}" # safer, allows concatenation
echo "${name:-default}" # use default if unset
echo "${name:=default}" # set and use default if unset Environment Variables
Variables are local to the current shell unless exported. Child processes inherit exported variables but cannot modify the parent's environment.
export PATH="/usr/local/bin:$PATH"
export API_KEY="secret123"
env # list all environment variables
printenv HOME # print specific env variable
unset API_KEY # remove variable Special Variables
Bash provides built-in variables for script arguments, process IDs, and exit codes. These are essential for writing reusable scripts.
$0 # script name
$1 $2 ... # positional parameters
$# # number of arguments
$* # all args as single word
$@ # all args as separate words (always quote this)
$? # exit status of last command
$$ # PID of current shell
$! # PID of last background command
$_ # last argument of previous command Parameter Expansion
Parameter expansion is one of bash's most powerful and underutilized features. It provides default values, substring extraction, pattern replacement, and length calculation without spawning external processes.
${var:-default} # default if unset/null
${var:=default} # assign default if unset/null
${var:?message} # error and exit if unset/null
${var:+replacement} # replacement if set/non-null
${#var} # length of variable
${var:offset} # substring from offset
${var:offset:length} # substring with length
${var#pattern} # remove shortest prefix match
${var##pattern} # remove longest prefix match
${var%pattern} # remove shortest suffix match
${var%%pattern} # remove longest suffix match
${var/pattern/repl} # replace first match
${var//pattern/repl} # replace all matches Conditionals
Bash provides if, case, and the test command for conditional execution. The [[ ]] construct is preferred over [ ] for string comparisons because it supports pattern matching and regex without quoting complications.
if Statement
The basic conditional structure supports if, elif, and else branches. The then keyword must follow the condition on the same line or on the next line.
if [ $age -gt 18 ]; then
echo "Adult"
elif [ $age -eq 18 ]; then
echo "Just turned adult"
else
echo "Minor"
fi
# One-liner using && and ||
[ $x -eq 1 ] && echo "yes" || echo "no" Test Operators — Numeric
Integer comparisons use -eq, -ne, -lt, -le, -gt, and -ge. Inside (( )) arithmetic context, you can use C-style operators.
[ $a -eq $b ] # equal
[ $a -ne $b ] # not equal
[ $a -lt $b ] # less than
[ $a -le $b ] # less than or equal
[ $a -gt $b ] # greater than
[ $a -ge $b ] # greater than or equal
# In (( )) you can use familiar operators:
(( a == b ))
(( a > b )) Test Operators — Strings
String tests check for emptiness, equality, and pattern matching. Always quote string variables in test expressions.
[ "$str" ] # non-empty string
[ -z "$str" ] # zero length / empty
[ -n "$str" ] # non-zero length
[ "$a" = "$b" ] # strings equal
[ "$a" != "$b" ] # strings not equal
[[ "$a" == "x*" ]] # pattern match in [[ ]]
[[ "$a" =~ regex ]] # regex match (bash 3.0+) case Statement
case provides pattern matching for multiple conditions. It is cleaner than a chain of if/elif when comparing a single value against multiple patterns. Patterns support glob-style wildcards and the | alternation operator.
case $os in
Linux|linux|GNU)
echo "Linux system"
;;
Darwin)
echo "macOS system"
;;
CYGWIN|MINGW|MSYS)
echo "Windows subsystem"
;;
*)
echo "Unknown: $os"
;;
esac Logical Operators
Conditions can be combined with && (AND), || (OR), and ! (NOT). Inside [[ ]], these operators can be used directly within the brackets.
[ $a -eq 1 ] && [ $b -eq 2 ] # AND
[ $a -eq 1 ] || [ $b -eq 2 ] # OR
! [ $a -eq 1 ] # NOT
[[ $a == "x" && $b == "y" ]]
[[ $a == "x" || $b == "y" ]] Loops
Bash provides three loop constructs: for for iterating over lists, while for conditional repetition, and until for looping until a condition becomes true.
for Loop — List
The for loop iterates over a whitespace-separated list of words. For lists that might contain spaces, use arrays or quote appropriately.
for item in apple banana cherry; do
echo "Fruit: $item"
done
# C-style for loop (bash extension)
for (( i=0; i<10; i++ )); do
echo "Iteration $i"
done for Loop — Files
Iterating over files is one of the most common loop use cases. The glob pattern expands to matching filenames before the loop runs.
for file in *.txt; do
echo "Processing $file"
done
# Recursive iteration with find
while IFS= read -r -d '' file; do
echo "Found: $file"
done < <(find . -name "*.sh" -print0) while Loop
The while loop executes as long as its condition is true. It is ideal for reading files line by line and for counter-based loops.
count=0
while [ $count -lt 5 ]; do
echo "Count: $count"
((count++))
done
# Read file line by line
while IFS= read -r line; do
echo "Line: $line"
done < file.txt until Loop
The until loop is the inverse of while — it executes until its condition becomes true. It is useful for waiting operations and retry logic.
count=0
until [ $count -ge 5 ]; do
echo "Count: $count"
((count++))
done Loop Control
break exits the loop entirely. continue skips to the next iteration. Both accept an optional numeric argument to break or continue out of nested loops.
for i in {1..10}; do
[ $i -eq 3 ] && continue # skip iteration 3
[ $i -eq 7 ] && break # exit loop at 7
echo $i
done Functions
Bash functions group reusable code blocks. They can accept arguments, return exit codes, and capture output into variables. Local variables prevent functions from polluting the global namespace.
Function Definition
Functions can be defined with the function keyword or without it. Both styles are equivalent in bash.
# Style 1
function greet() {
local name=$1 # local variable
echo "Hello, $name!"
}
# Style 2 (no keyword)
greet() {
echo "Hello, $1!"
}
# Call
greet "World" # output: Hello, World!
result=$(greet "World") # capture output Return Values
Bash functions can only return integer exit codes (0-255) via the return statement. To return strings, use echo and command substitution.
# Return exit code (0-255)
is_even() {
local n=$1
(( n % 2 == 0 )) && return 0 || return 1
}
# Return string via echo
get_full_name() {
local first=$1 last=$2
echo "$first $last"
}
name=$(get_full_name "John" "Doe")
# Check return code
is_even 4 && echo "Even" || echo "Odd" Function Arguments
Function arguments use the same positional parameter variables as scripts: $1, $2, $#, $@. The shift command removes $1 and shifts remaining arguments left.
process() {
echo "First: $1"
echo "Second: $2"
echo "All: $@"
echo "Count: $#"
shift # shift args left (removes $1)
echo "After shift: $1"
}
process one two three String Manipulation
Bash provides extensive string manipulation through parameter expansion. These operations are fast because they run entirely within the shell without spawning external processes like sed or awk.
String Length and Extraction
str="Hello, World!"
echo "${#str}" # length: 13
echo "${str:0:5}" # substring: Hello
echo "${str:7}" # from index 7: World!
echo "${str: -6}" # last 6 chars: World!
echo "${str: -6:5}" # last 6, then 5: World String Replacement
str="/usr/local/bin:/usr/bin:/bin"
echo "${str/usr/opt}" # replace first
echo "${str//usr/opt}" # replace all
echo "${str/#\/usr/\/opt}" # replace prefix
echo "${str/%\/bin/\/sbin}" # replace suffix
echo "${str//usr/}" # delete all occurrences Case Conversion
str="Hello World"
echo "${str^^}" # uppercase: HELLO WORLD
echo "${str,,}" # lowercase: hello world
echo "${str~~}" # swap case: hELLO wORLD
echo "${str^}" # first char uppercase
echo "${str,}" # first char lowercase Trimming
The # and % operators remove prefix and suffix patterns. A single operator removes the shortest match; doubling it removes the longest match.
path="/home/user/file.txt"
echo "${path#*/}" # remove shortest prefix: home/user/file.txt
echo "${path##*/}" # remove longest prefix: file.txt
echo "${path%.*}" # remove shortest suffix: /home/user/file
echo "${path%%.*}" # remove longest suffix: /home/user/file Arrays
Bash supports both indexed arrays (ordered lists) and associative arrays (key-value pairs, bash 4.0+). Arrays are the correct way to handle lists of values that might contain spaces.
Indexed Arrays
fruits=(apple banana cherry)
numbers=(10 20 30 40 50)
echo "${fruits[0]}" # apple (first element)
echo "${fruits[-1]}" # cherry (last, bash 4.2+)
echo "${#fruits[@]}" # 3 (array length)
echo "${fruits[@]}" # all elements
echo "${!fruits[@]}" # all indices Array Operations
arr=(a b c)
arr+=(d e) # append
arr[3]=f # insert at index
unset arr[1] # remove element
echo "${arr[@]:1:2}" # slice elements 1-2
IFS=','; echo "${arr[*]}"; unset IFS # join with delimiter Associative Arrays
declare -A user
user["name"]="John"
user["email"]="john@example.com"
user["role"]="admin"
echo "${user[name]}" # access value
echo "${#user[@]}" # number of keys
echo "${!user[@]}" # all keys
echo "${user[@]}" # all values
for key in "${!user[@]}"; do
echo "$key: ${user[$key]}"
done Redirection and Pipes
Redirection and pipes are the Unix philosophy in action: small programs connected by streams. Bash provides powerful operators for routing input, output, and errors.
Output Redirection
command > file.txt # stdout to file (overwrite)
command >> file.txt # stdout to file (append)
command 2> error.log # stderr to file
command &> output.log # stdout and stderr to file
command > /dev/null # discard stdout
command 2>&1 # redirect stderr to stdout
command > file.txt 2>&1 # both to file Input Redirection
command < input.txt # read stdin from file
command << EOF
line1
line2
EOF
command <<< "input string" # here-string
diff <(sort file1) <(sort file2) # process substitution Pipes
cat file.txt | grep "pattern" | sort | uniq | wc -l
echo "data" | tee log.txt | grep "filter"
# Named pipes (FIFO)
mkfifo mypipe
echo "hello" > mypipe &
cat < mypipe File Test Operators
File tests check the existence, type, and permissions of files and directories. These are essential for writing robust scripts that handle missing files gracefully.
File Existence and Type
[ -e file ] # exists
[ -f file ] # regular file
[ -d file ] # directory
[ -L file ] # symbolic link
[ -b file ] # block device
[ -c file ] # character device
[ -p file ] # named pipe
[ -S file ] # socket File Permissions
[ -r file ] # readable
[ -w file ] # writable
[ -x file ] # executable
[ -s file ] # size > 0 (non-empty)
[ -O file ] # owned by effective UID
[ -G file ] # owned by effective GID File Comparison
[ file1 -nt file2 ] # file1 newer than file2
[ file1 -ot file2 ] # file1 older than file2
[ file1 -ef file2 ] # same physical file (same inode) Arithmetic
Bash performs integer arithmetic in $(( )) arithmetic expansion. Floating-point math requires external tools like bc or awk.
Arithmetic Expansion
result=$(( 5 + 3 ))
echo $(( 10 - 4 ))
echo $(( 6 * 7 ))
echo $(( 20 / 3 )) # integer division: 6
echo $(( 20 % 3 )) # modulo: 2
echo $(( 2 ** 10 )) # power: 1024 (bash 4.0+)
((x++)) # increment
((x--)) # decrement
((x += 5)) # compound assignment Bitwise and Logical
echo $(( 5 & 3 )) # AND: 1
echo $(( 5 | 3 )) # OR: 7
echo $(( 5 ^ 3 )) # XOR: 6
echo $(( ~5 )) # NOT
echo $(( 16 << 2 )) # left shift: 64
echo $(( 16 >> 2 )) # right shift: 4 let Command
let x=5+3
let "y = x * 2"
let "a++"
let "b += 10"
if let "x % 2 == 0"; then
echo "Even"
fi Exit Codes and Error Handling
Every command returns an exit code: zero for success, non-zero for failure. Scripts that ignore exit codes silently continue past errors, producing confusing and dangerous behavior.
Exit Codes Reference
0 # Success
1 # General error
2 # Misuse of shell builtins
126 # Command invoked cannot execute
127 # Command not found
128+N # Fatal signal N (e.g., 130 = Ctrl+C/SIGINT)
130 # Script terminated by Ctrl+C
137 # Killed with SIGKILL (kill -9)
143 # Killed with SIGTERM Strict Mode
The set -euo pipefail combination enables strict error handling. -e exits on error, -u exits on undefined variables, and -o pipefail causes pipelines to fail if any command fails.
set -euo pipefail
# Trap signals
trap 'echo "Interrupted"; exit 1' INT TERM
# Cleanup on exit
tempfile=$(mktemp)
trap 'rm -f "$tempfile"' EXIT Useful One-liners
These single-line commands solve common problems without writing full scripts.
# Read file into array
mapfile -t lines < file.txt
# Default value if variable is empty
name="${1:-default}"
# Check if command exists
command -v git &> /dev/null && echo "git installed"
# Get script directory
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# Parallel processing with xargs
cat urls.txt | xargs -P 4 -n 1 curl -s -o /dev/null Script Template
This template provides a robust starting point for new bash scripts. It includes strict mode, argument parsing, help text, and cleanup handling.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
SCRIPT_NAME=$(basename "$0")
usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Options:
-h, --help Show this help
-v, --verbose Enable verbose output
EOF
}
main() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) usage; exit 0 ;;
-v|--verbose) VERBOSE=1; shift ;;
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
esac
done
echo "Hello, World!"
}
main "$@" The Interactive Cheat Sheet
Our free interactive bash scripting cheat sheet organizes seventy-plus advanced patterns into eleven categories with real-time search, category filtering, and one-click copy. Each pattern includes syntax highlighting with color-coded keywords, strings, variables, numbers, comments, and operators.
The Submarine Control Room aesthetic uses deep ocean-blue backgrounds, fluorescent cyan and amber accents, a subtle sonar pulse animation, and precision instrument typography. The design reinforces bash scripting as a mission-critical craft — each command calibrated, each script running silent and deep.
Everything is 100% client-side. Your script experiments and command references never leave your browser.
Related Tools and References
Bash scripting interacts with virtually every aspect of system administration and development. These related DevToolkit resources cover adjacent topics:
- Terminal & Shell Commands Cheat Sheet — 80+ Linux, macOS, and Bash commands with the Mainframe Operator's Console aesthetic.
- Linux File Permissions Cheat Sheet — Interactive chmod calculator, 50+ permission commands, and reference tables.
- Git Commands Cheat Sheet — 100+ Git commands covering branching, merging, rebasing, and history manipulation.
- Docker Commands Cheat Sheet — 90+ Docker and Docker Compose commands for container management.
- Regex Tester — Interactive regular expression testing with match highlighting and explanations.
- VS Code Shortcuts Cheat Sheet — 80+ keyboard shortcuts for the most popular code editor.
- HTTP Status Codes Reference — Complete guide to HTTP response codes and their meanings.
Bookmark the Bash Scripting Cheat Sheet for instant access whenever you need to write a script, look up syntax, or remember a parameter expansion trick. Whether you are automating deployments, writing system administration tools, or studying for a Linux certification, this reference provides the commands and concepts you need.