Quick reference for Bash scripting: variables, parameter expansion, conditionals, loops, functions, arrays, redirection, error handling, and strict mode.
Bash scripting is the foundation of Unix/Linux automation — from simple one-liners to full deployment scripts. This cheat sheet covers essential Bash syntax: variables, conditionals, loops, functions, arrays, parameter expansion, redirection, and error handling. Organized by concept for fast lookup when writing or debugging shell scripts.
Variables and Quoting
| Syntax | Description |
|---|
| name="value" | Assign a variable (no spaces around =) |
| $name or ${name} | Expand a variable |
| readonly name="val" | Declare a read-only (constant) variable |
| local name="val" | Declare a function-local variable |
| export name="val" | Export variable to child processes |
| unset name | Remove a variable |
| "double quotes" | Allow variable expansion and command substitution |
| 'single quotes' | Literal string — no expansion at all |
| $(command) | Command substitution — capture output of a command |
| $((expression)) | Arithmetic expansion — e.g. $((a + b)) |
How Do You Use Special Variables?
| Variable | Description |
|---|
| $0 | Name of the script or shell |
| $1, $2, ... | Positional parameters (arguments passed to script/function) |
| $# | Number of positional parameters |
| $@ | All positional parameters as separate words |
| $* | All positional parameters as a single word |
| $? | Exit status of the last command (0 = success) |
| $$ | PID of the current shell |
| $! | PID of the last background process |
| $_ | Last argument of the previous command |
| ${PIPESTATUS[@]} | Array of exit statuses from the last pipeline |
Parameter Expansion
| Expression | Description |
|---|
| ${var:-default} | Use "default" if var is unset or null |
| ${var:=default} | Assign "default" if var is unset or null |
| ${var:+value} | Use "value" only if var is set (not null) |
| ${var:?error msg} | Print error and exit if var is unset or null |
| ${#var} | Length of the string in var |
| ${var:offset:length} | Substring extraction — e.g. ${name:0:3} |
| ${var#pattern} | Remove shortest matching prefix |
| ${var##pattern} | Remove longest matching prefix |
| ${var%pattern} | Remove shortest matching suffix |
| ${var%%pattern} | Remove longest matching suffix |
| ${var/old/new} | Replace first occurrence of "old" with "new" |
| ${var//old/new} | Replace all occurrences of "old" with "new" |
| ${var,,} | Convert entire string to lowercase |
| ${var^^} | Convert entire string to uppercase |
Conditionals and Test Expressions
| Syntax | Description |
|---|
| if [[ cond ]]; then ... fi | Basic if statement |
| if [[ cond ]]; then ... elif [[ cond ]]; then ... else ... fi | Full if/elif/else block |
| [[ -z "$str" ]] | True if string is empty |
| [[ -n "$str" ]] | True if string is not empty |
| [[ "$a" == "$b" ]] | String equality |
| [[ "$a" != "$b" ]] | String inequality |
| [[ "$str" =~ regex ]] | Regex match (extended regex) |
| [[ $num -eq $num2 ]] | Numeric equality (-ne, -lt, -le, -gt, -ge) |
| (( num < num2 )) | Arithmetic comparison (supports <, >, <=, >=, ==, !=) |
| [[ cond1 && cond2 ]] | Logical AND inside [[ ]] |
| [[ cond1 || cond2 ]] | Logical OR inside [[ ]] |
| [[ ! cond ]] | Logical NOT |
File Test Operators
| Test | Description |
|---|
| [[ -e "$file" ]] | File exists (any type) |
| [[ -f "$file" ]] | Regular file exists |
| [[ -d "$dir" ]] | Directory exists |
| [[ -r "$file" ]] | File is readable |
| [[ -w "$file" ]] | File is writable |
| [[ -x "$file" ]] | File is executable |
| [[ -s "$file" ]] | File exists and is not empty |
| [[ -h "$file" ]] | File is a symbolic link |
| [[ "$f1" -nt "$f2" ]] | f1 is newer than f2 |
| [[ "$f1" -ot "$f2" ]] | f1 is older than f2 |
How Do You Write Loops in Bash?
| Pattern | Description |
|---|
| for item in list; do ... done | Iterate over a list of words |
| for file in *.txt; do ... done | Iterate over files matching a glob |
| for i in {1..10}; do ... done | Iterate over a numeric range |
| for i in {0..20..5}; do ... done | Range with step — 0, 5, 10, 15, 20 |
| for ((i=0; i<10; i++)); do ... done | C-style for loop |
| while [[ cond ]]; do ... done | While loop — runs while condition is true |
| until [[ cond ]]; do ... done | Until loop — runs until condition is true |
| while read -r line; do ... done < file | Read a file line by line |
| while IFS=, read -r a b c; do ... done < data.csv | Parse CSV fields into variables |
| break | Exit the innermost loop |
| continue | Skip to the next iteration |
Functions
| Syntax | Description |
|---|
| fname() { ... } | Define a function (preferred POSIX form) |
| local var="value" | Declare a local variable inside a function |
| $1, $2, ... | Access function arguments positionally |
| $# inside function | Number of arguments passed to the function |
| return 0 | Return an exit status (0-255) from a function |
| result=$(fname arg1) | Capture function stdout as a value |
| fname() { echo "$1"; }; fname "hi" | Define and call a function with an argument |
Arrays
| Syntax | Description |
|---|
| arr=("a" "b" "c") | Declare an indexed array |
| ${arr[0]} | Access element at index 0 |
| ${arr[-1]} | Access last element |
| ${arr[@]} | All elements as separate words |
| ${#arr[@]} | Number of elements |
| ${!arr[@]} | All indices |
| arr+=("new") | Append an element |
| unset arr[1] | Remove element at index 1 |
| ${arr[@]:2:3} | Slice — 3 elements starting at index 2 |
| declare -A map | Declare an associative array (dictionary) |
| map[key]="value" | Set a key-value pair in an associative array |
| ${map[key]} | Get value by key |
| ${!map[@]} | All keys of an associative array |
Redirection and Pipes
| Syntax | Description |
|---|
| cmd > file | Redirect stdout to file (overwrite) |
| cmd >> file | Redirect stdout to file (append) |
| cmd 2> file | Redirect stderr to file |
| cmd 2>&1 | Redirect stderr to stdout |
| cmd &> file | Redirect both stdout and stderr to file |
| cmd &>/dev/null | Discard all output |
| cmd < file | Read stdin from file |
| cmd <<< "string" | Here-string — pass string as stdin |
| cmd1 | cmd2 | Pipe stdout of cmd1 into stdin of cmd2 |
| cmd1 |& cmd2 | Pipe both stdout and stderr into cmd2 |
| diff <(cmd1) <(cmd2) | Process substitution — compare output of two commands |
| cat <<EOF ... EOF | Here-document — multi-line input block |
Error Handling and Strict Mode
| Command | Description |
|---|
| set -e | Exit immediately if any command fails |
| set -u | Treat unset variables as an error |
| set -o pipefail | Pipeline fails if any command in the pipe fails |
| set -euo pipefail | Strict mode — combine all three above |
| trap "cleanup" EXIT | Run cleanup function on script exit |
| trap "echo err at $LINENO" ERR | Catch errors with line number |
| cmd || true | Prevent set -e from exiting on expected failure |
| cmd || { echo "failed"; exit 1; } | Handle failure inline |
| if ! cmd; then ... fi | Branch on command failure without triggering set -e |
Frequently Asked Questions
What does set -euo pipefail do in Bash?
set -e exits the script on the first error, set -u treats unset variables as errors, and set -o pipefail causes a pipeline to fail if any command in it fails. Together they form "strict mode" and are recommended at the top of every Bash script to catch bugs early.
What is the difference between $@ and $* in Bash?
When double-quoted, "$@" expands each positional parameter as a separate word, preserving whitespace in arguments. "$*" joins all parameters into a single word separated by the first character of IFS. In almost all cases you want "$@" to correctly handle arguments containing spaces.
How do you check if a file exists in a Bash script?
Use if [[ -f "$file" ]]; then ... fi to check for a regular file. Use -e for any file type, -d for directories, and -s to check that a file exists and is non-empty.
How do you read a file line by line in Bash?
Use: while IFS= read -r line; do echo "$line"; done < file.txt. The -r flag prevents backslash interpretation and IFS= preserves leading/trailing whitespace. Never use "for line in $(cat file)" as it splits on words, not lines.
What is the difference between [[ ]] and [ ] in Bash?
[[ ]] is a Bash keyword that supports regex matching (=~), pattern matching, and logical operators (&&, ||) without quoting issues. [ ] (or test) is a POSIX command that requires careful quoting and uses -a/-o for logic. Prefer [[ ]] in Bash scripts for safer, more readable conditionals.
How do you pass and return values from a Bash function?
Pass values as positional arguments ($1, $2, etc.). Bash functions cannot return strings directly — "return" only sets an exit status (0-255). To return a value, echo it from the function and capture with result=$(myfunc arg1). Use local to avoid polluting the global scope.