| name | coding-bash-reference |
| description | Use when writing, reviewing, or debugging Bash scripts, when needing exact syntax for shell constructs, parameter expansions, redirections, builtins, test expressions, arrays, or any Bash 5.3 feature. Use when unsure about quoting rules, expansion order, conditional operators, trap behavior, shopt options, or specific builtin options and arguments. |
Bash 5.3 Reference
Overview
Complete reference for GNU Bash 5.3 (May 2025). Covers all shell syntax, builtins, variables, expansions, redirections, and features. Use this skill whenever writing or reviewing bash scripts to ensure correctness.
This file is a scannable hub. Each section below summarizes key constructs with compact tables. Use the Read tool to load referenced files identified as relevant for full syntax, edge cases, and examples:
- Syntax & commands (quoting, compound commands, pipelines):
shell-syntax-and-commands.md
- Functions, parameters & expansions (all expansion types, pattern matching):
functions-parameters-expansions.md
- Redirections & execution (I/O, here docs, FDs, command search, signals):
redirections-and-execution.md
- Builtins (
set, shopt, declare, read, printf, trap, etc.): shell-builtins.md
- Variables (
BASH_REMATCH, PIPESTATUS, EPOCHSECONDS, etc.): shell-variables.md
- Bash features (startup files, arrays, arithmetic, POSIX mode, conditionals):
bash-features.md
- Job control, readline & history (bg/fg, completion, history expansion):
job-control-readline-history.md
When to Use
- Need exact syntax for any Bash construct (loops, conditionals, expansions, redirections)
- Writing, reviewing, or debugging Bash scripts
- Unsure about quoting rules, expansion order, or operator behavior
- Need to look up a specific builtin, variable, shopt option, or test operator
- Verifying Bash 5.3-specific features (
${ command; }, GLOBSORT, BASH_MONOSECONDS)
When NOT to Use
- Writing POSIX-portable
sh scripts (this covers Bash-specific features beyond POSIX)
- Zsh, Fish, or other shell syntax
- Structuring multi-file Bash projects (use
bitranox:coding-bash-clean-architecture instead)
Before you reach for Bash (and before you ship)
- Prefer a Python script over shell for any real logic (global working rule). Shell has sharp edges
that bite repeatedly - a leading-dash path makes
dirname/grep treat it as an option, sed -i
can double-apply, and cmd | head reports head's exit status not cmd's. For path handling, text
transforms, or multi-step automation, write a Python (stdlib) script. Reserve Bash for a simple
one-shot command invocation (including launching a Python script).
- Gate every Bash script you DO ship with
shellcheck and bash -n before committing - required
checks, not optional. shellcheck -x script.sh (follows sourced files) catches quoting,
word-splitting, and unset-variable bugs; bash -n script.sh catches syntax errors without executing
it. Add shfmt -i 4 -d script.sh where the project formats shell.
Reference Files
| File | Contents |
|---|
shell-syntax-and-commands.md | Quoting, comments, reserved words, pipelines, lists, compound commands (if/for/while/case/select/[[/(( ), grouping, coprocesses |
functions-parameters-expansions.md | Shell functions, positional/special parameters, ALL expansion types (brace, tilde, parameter, command substitution, arithmetic, process substitution, word splitting, globbing, pattern matching) |
redirections-and-execution.md | All redirection types, here docs/strings, file descriptors, command search/execution, execution environment, exit status, signals, shell scripts |
shell-builtins.md | All Bourne shell builtins, all Bash builtins, set options, shopt options, special builtins |
shell-variables.md | All Bourne shell variables, all Bash variables (BASH_, COMP_, HIST*, READLINE_*, etc.) |
bash-features.md | Invocation options, startup files, interactive shell behavior, conditional expressions, arithmetic, aliases, arrays, directory stack, prompt control, restricted shell, POSIX mode, compatibility mode |
job-control-readline-history.md | Job control (bg/fg/jobs/disown), readline configuration, all bindable commands, vi mode, programmable completion (complete/compgen/compopt), history expansion |
Which File Do I Need?
| I need to... | Read |
|---|
| Write a function, use parameters/expansions, do string manipulation | functions-parameters-expansions.md |
Use if/for/while/case/select/[[ ]]/(( )), or understand quoting | shell-syntax-and-commands.md |
| Redirect I/O, use here docs/strings, understand fd management | redirections-and-execution.md |
Look up a builtin (set, shopt, declare, read, printf, trap, etc.) | shell-builtins.md |
Check a shell variable (BASH_REMATCH, PIPESTATUS, EPOCHSECONDS, etc.) | shell-variables.md |
| Understand startup files, arrays, arithmetic, POSIX mode, or conditional expressions | bash-features.md |
| Work with job control, readline, completion, or history expansion | job-control-readline-history.md |
Quick Reference: Most-Used Constructs
Quoting
Full details: shell-syntax-and-commands.md (section 3.1.2, Quoting)
| Syntax | Behavior |
|---|
\x | Escape single character |
'text' | Literal string, no expansion |
"text" | Allows $, `, \, ! expansion |
$'text' | ANSI-C escapes: \n, \t, \e, \xHH, \uHHHH, \UHHHHHHHH |
$"text" | Locale-specific translation |
Parameter Expansion
Full details: functions-parameters-expansions.md (section 3.5.3)
| Syntax | Result |
|---|
${var:-default} | Use default if var unset/null |
${var:=default} | Assign default if var unset/null |
${var:+alternate} | Use alternate if var IS set |
${var:?error} | Error if var unset/null |
${#var} | String length |
${var:offset:length} | Substring |
${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/pat/str} | Replace first match |
${var//pat/str} | Replace all matches |
${var/#pat/str} | Replace if matches beginning |
${var/%pat/str} | Replace if matches end |
${var^pattern} | Uppercase first char |
${var^^pattern} | Uppercase all chars |
${var,pattern} | Lowercase first char |
${var,,pattern} | Lowercase all chars |
${!prefix*} | Names matching prefix |
${!name[@]} | Array indices/keys |
${!var} | Indirect expansion |
${var@Q} | Quote for reuse |
${var@E} | Expand escape sequences |
${var@P} | Expand as prompt string |
${var@A} | Assignment statement form |
${var@a} | Attribute flags |
${var@U} | Uppercase all |
${var@u} | Uppercase first |
${var@L} | Lowercase all |
${var@K} | Key-value pairs (assoc arrays) |
Special Parameters
Full details: functions-parameters-expansions.md (section 3.4.2, Special Parameters)
| Param | Meaning |
|---|
$0 | Script/shell name |
$1..$9, ${10} | Positional parameters |
$# | Number of positional parameters |
$* | All positional params as single word (with IFS) |
$@ | All positional params as separate words |
"$*" | "$1c$2c..." where c = first char of IFS |
"$@" | "$1" "$2" ... (preserves word boundaries) |
$? | Exit status of last command |
$$ | PID of the shell |
$! | PID of last background command |
$- | Current option flags |
$_ | Last argument of previous command |
Compound Commands
Full details: shell-syntax-and-commands.md (section 3.2.5, Compound Commands)
if cmd; then ...; elif cmd; then ...; else ...; fi
for var in words; do ...; done
for (( init; test; step )); do ...; done
while cmd; do ...; done
until cmd; do ...; done
case word in
pattern1|pattern2) commands ;;
pattern3) commands ;&
pattern4) commands ;;&
esac
select var in words; do ...; done
[[ expression ]]
(( expression ))
{ commands; }
( commands )
Test Operators ([[ ]] and test/[ ])
Full details: bash-features.md (section 6.4, Bash Conditional Expressions)
| Operator | Test |
|---|
-e file | Exists |
-f file | Regular file |
-d file | Directory |
-L file / -h file | Symlink |
-s file | Non-zero size |
-r file | Readable |
-w file | Writable |
-x file | Executable |
-p file | Named pipe |
-S file | Socket |
-b file | Block device |
-c file | Character device |
-t fd | FD is terminal |
-O file | Owned by effective UID |
-G file | Owned by effective GID |
-N file | Modified since last read |
f1 -nt f2 | f1 newer than f2 |
f1 -ot f2 | f1 older than f2 |
f1 -ef f2 | Same inode |
-v var | Variable is set |
-R var | Variable is nameref |
-z string | Zero length |
-n string | Non-zero length |
s1 == s2 | Equal (pattern match in [[ ]]) |
s1 != s2 | Not equal |
s1 < s2 | Less than (lexicographic) |
s1 > s2 | Greater than (lexicographic) |
s1 =~ regex | Regex match ([[ ]] only) |
n1 -eq n2 | Numeric equal |
n1 -ne n2 | Numeric not equal |
n1 -lt n2 | Numeric less than |
n1 -le n2 | Numeric less/equal |
n1 -gt n2 | Numeric greater than |
n1 -ge n2 | Numeric greater/equal |
Redirections
Full details: redirections-and-execution.md (section 3.6, Redirections)
| Syntax | Operation |
|---|
cmd < file | Stdin from file |
cmd > file | Stdout to file (truncate) |
cmd >> file | Stdout to file (append) |
cmd 2> file | Stderr to file |
cmd &> file or cmd > file 2>&1 | Stdout+stderr to file |
cmd &>> file or cmd >> file 2>&1 | Stdout+stderr append |
cmd >| file | Force overwrite (noclobber) |
cmd <<EOF | Here document |
cmd <<-EOF | Here document (strip leading tabs) |
cmd <<< "string" | Here string |
cmd <&fd | Duplicate input FD |
cmd >&fd | Duplicate output FD |
cmd fd<&- | Close input FD |
cmd fd>&- | Close output FD |
cmd n<>file | Open for read+write on FD n |
cmd {var}> file | Auto-assign FD to var |
Special filenames in redirections: /dev/fd/N, /dev/stdin, /dev/stdout, /dev/stderr, /dev/tcp/host/port, /dev/udp/host/port
Arrays
Full details: bash-features.md (section 6.7, Arrays)
declare -a arr=(one two three)
arr[0]="value"
arr+=(more items)
declare -A map=([key1]=val1 [key2]=val2)
map[key]="value"
${arr[0]}
${arr[@]}
${arr[*]}
${#arr[@]}
${!arr[@]}
${arr[@]:off:len}
unset 'arr[2]'
unset arr
Expansion Order
- Brace expansion
- Tilde expansion
- Parameter and variable expansion
- Arithmetic expansion
- Command substitution (left-to-right)
- Process substitution
- Word splitting
- Filename expansion (globbing)
- Quote removal
Steps 2-6 happen left-to-right simultaneously. Full details with word-count impacts: functions-parameters-expansions.md (section 3.5).
Common set Options
Full details: shell-builtins.md (section 4.3.1, the set builtin)
| Option | Effect |
|---|
set -e (errexit) | Exit on error (with exceptions) |
set -u (nounset) | Error on unset variables |
set -o pipefail | Pipeline fails if any command fails |
set -x (xtrace) | Print commands before execution |
set -f (noglob) | Disable filename expansion |
set -n (noexec) | Read commands without executing (syntax check) |
set -o posix | POSIX compliance mode |
set -E (errtrace) | ERR trap inherited by functions |
set -T (functrace) | DEBUG/RETURN traps inherited by functions |
Essential shopt Options
Full details: shell-builtins.md (section 4.3.2, the shopt builtin)
| Option | Effect |
|---|
extglob | Extended patterns: ?(pat) *(pat) +(pat) @(pat) !(pat) |
globstar | ** matches directories recursively |
nullglob | Unmatched globs expand to nothing |
failglob | Unmatched globs cause error |
nocaseglob | Case-insensitive globbing |
nocasematch | Case-insensitive case and [[ == ]] |
dotglob | Globs match dotfiles |
lastpipe | Last pipeline command runs in current shell |
inherit_errexit | Command substitutions inherit errexit |
assoc_expand_once | Expand associative array subscripts once |
Trap Signals
Full details: shell-builtins.md (section 4.1, the trap builtin) and redirections-and-execution.md (section 3.7.6, Signals)
trap 'cleanup' EXIT
trap 'handle_err' ERR
trap 'on_debug' DEBUG
trap 'on_return' RETURN
trap 'handle_int' INT
trap 'handle_term' TERM
trap '' SIGNAL
trap - SIGNAL
Arithmetic Operators (inside (( )) and $(( )))
Full details: bash-features.md (section 6.5, Shell Arithmetic)
All C-style operators: +, -, *, /, %, ** (exponent), <<, >>, &, |, ^, ~, !, &&, ||, <, >, <=, >=, ==, !=, =, +=, -=, *=, /=, %=, <<=, >>=, &=, |=, ^=, ++, --, expr?expr:expr (ternary), expr,expr (comma)
Bases: 0x (hex), 0 (octal), base#number (arbitrary base 2-64, e.g. 2#101 for binary). Bash has no 0b binary prefix.
Prompt Escape Sequences
Full details: bash-features.md (section 6.9, Controlling the Prompt)
| Escape | Meaning |
|---|
\u | Username |
\h | Hostname (short) |
\H | Hostname (full) |
\w | Working directory |
\W | Basename of working directory |
\d | Date (Day Mon Date) |
\t | Time (HH:MM:SS 24hr) |
\T | Time (HH:MM:SS 12hr) |
\@ | Time (AM/PM) |
\A | Time (HH:MM 24hr) |
\D{fmt} | strftime format |
\j | Number of jobs |
\! | History number |
\# | Command number |
\$ | # if root, $ otherwise |
\[ | Begin non-printing chars |
\] | End non-printing chars |
Definitions
| Term | Definition |
|---|
| blank | Space or tab |
| word | Sequence of characters treated as a unit (no unquoted metacharacters) |
| token | A word or an operator |
| metacharacter | Unquoted: space, tab, newline, |, &, ;, (, ), <, > |
| control operator | ||, &&, &, ;, ;;, ;&, ;;&, |, |&, (, ), newline |
| name/identifier | Letters, numbers, underscores; starts with letter or underscore |
| exit status | 0-255; 0 = success, 1 = general error, 2 = usage error, 126 = not executable, 127 = not found, 128+N = killed by signal N |
| special builtin | POSIX-designated builtins that have special properties (break, :, ., continue, eval, exec, exit, export, readonly, return, set, shift, trap, unset) |
Common Patterns
Safe Script Header
#!/usr/bin/env bash
set -euo pipefail
Temporary Files
tmpfile=$(mktemp) || exit 1
trap 'rm -f "$tmpfile"' EXIT
Read File Line by Line
while IFS= read -r line; do
printf '%s\n' "$line"
done < "$file"
Default Values
name="${1:-default}"
name="${1:?'missing arg'}"
String Operations
lower="${str,,}"
upper="${str^^}"
first_cap="${str^}"
filename="${path##*/}"
dir="${path%/*}"
ext="${file##*.}"
noext="${file%.*}"
Array Iteration
for item in "${arr[@]}"; do echo "$item"; done
for i in "${!arr[@]}"; do echo "$i: ${arr[$i]}"; done
Process Substitution
diff <(sort file1) <(sort file2)
while IFS= read -r line; do ...; done < <(cmd)
Associative Array Check
declare -A map
if [[ -v map["key"] ]]; then echo "exists"; fi
Common Mistakes
Unquoted variables (word splitting + globbing)
for f in $files; do rm $f; done
for f in "${files[@]}"; do rm "$f"; done
[ ] vs [[ ]]
[ $var == "hello" ]
[[ $var == "hello" ]]
$@ vs $* quoting
for arg in $@; do echo "$arg"; done
for arg in "$@"; do echo "$arg"; done
set -e doesn't trigger everywhere
set -e
if false; then :; fi
false || true
false && true
false | true
! false
Missing ; before } in brace groups
{ echo "hello" }
{ echo "hello"; }
Array access without braces
arr=(one two three)
echo $arr[1]
echo "${arr[1]}"
Forgetting declare -A for associative arrays
map=([foo]=1 [bar]=2)
echo "${map[foo]}"
declare -A map=([foo]=1 [bar]=2)
echo "${map[foo]}"
Using = vs == in the wrong context
[ "$a" = "$b" ]
[[ "$a" == "$b" ]]
[[ "$a" == "$b"* ]]
[[ "$a" == $b ]]
Subshell variable loss in pipes
echo "hello" | read var
echo "$var"
read var < <(echo "hello")
echo "$var"
shopt -s lastpipe
echo "hello" | read var
local variables use dynamic scoping
inner() { echo "$x"; }
outer() { local x=42; inner; }
outer
Here-strings (<<<) add a trailing newline to the stream
cat <<< "hello" | od -An -tx1
wc -c <<< "hello"
read -r var <<< "hello"
printf '%s' "$var" | od -An -tx1
read -r var < <(printf '%s' "hello")
declare -i silently evaluates strings as arithmetic
declare -i num
num="1+1"
echo "$num"
For handling untrusted input at a script/CLI boundary in general (validate at the edge, never eval it,
pass argv not a shell string, escape per sink), see bitranox:coding-input-sanitization.
trap EXIT is reset in subshells
trap 'echo cleanup' EXIT
(echo "subshell")