A condensed, practical Bash reference for day-to-day scripting.

Not a Bash tutorial. Just the things that repeatedly matter in real scripts.

Bash-specific features are marked where relevant.


Quotes & Substitution

Pattern Meaning Example
'text' literal, no expansion '$HOME'
"text" expand vars & commands, preserve spaces "hi $USER"
$(cmd) command substitution $(pwd)
$'text' C-style escapes $'\n'

Why quoting matters

name="Tien Du"

mkdir $name

Expands into:

mkdir Tien Du

Creates two arguments instead of one.

Correct:

mkdir "$name"

Notes

  • Single quotes are safest for literal text.
  • Double quotes prevent accidental word splitting.
  • Prefer $(cmd) over old backticks: `cmd`.

Pipes & Redirection

Pattern Meaning Example
cmd1 | cmd2 pipe stdout ls | wc -l
cmd1 |& cmd2 pipe stdout+stderr make |& tee log
> overwrite stdout echo hi > x
>> append stdout echo hi >> x
< stdin <- file wc -l < x
2> stderr -> file cmd 2> err
&> stdout+stderr -> file cmd &> all.log
2>&1 stderr -> stdout cmd 2>&1
1>&2 stdout -> stderr echo hi 1>&2
<<< here-string grep hi <<< "$x"
<<EOF heredoc cat <<EOF
tee pipe + save echo hi | tee x
>| force overwrite echo hi >| x

File descriptors

FD Meaning
0 stdin
1 stdout
2 stderr

Example:

cmd > out.log 2> err.log
  • stdout -> out.log
  • stderr -> err.log

Portable version of &>:

cmd > out.log 2>&1

Process Substitution (Bash)

Pattern Meaning Example
<(cmd) cmd output as file diff <(sort a) <(sort b)
>(cmd) redirect into cmd echo hi > >(sed "s/h/H/")

Useful when a command expects filenames instead of stdin.


Parameter Expansion

Pattern Meaning Example
${v:-x} default if unset/empty ${a:-hi}
${v-x} default if unset ${a-hi}
${v:=x} assign default ${a:=hi}
${v:?msg} error if unset/empty ${a:?bad}
${v:+x} use x if set ${a:+yes}
${v#p} / ${v##p} trim front ${p##*/}
${v%p} / ${v%%p} trim back ${f%%.*}
${v/p/r} replace first ${x/-/_}
${v//p/r} replace all ${x//-/_}
${#v} length ${#s}
${v:pos} substring ${s:2}
${v:pos:len} bounded substring ${s:1:3}

Common patterns

Get filename:

path="/tmp/test.txt"

echo "${path##*/}"

Output:

test.txt

Remove extension:

echo "${path%.*}"

Output:

/tmp/test

Globs & Brace Expansion

Pattern Meaning Example
* wildcard *.txt
? single char a?.txt
[abc] char class file[1-9]
{a,b} alternation {dev,prod}.cfg
{1..5} sequence {1..3}

Example

mkdir -p project/{src,tests,docs}

Empty glob caveat

rm *.tmp

If nothing matches, Bash may pass literal *.tmp.

Safer:

shopt -s nullglob

Commands

Command Meaning Example
ln a b create hardlink ln file copy
ln -s a b create symlink ln -s /real/file link
readlink x show symlink target readlink link
readlink -f x resolve real path readlink -f link
unlink x remove link unlink link
ls -li inspect inode ls -li file copy
Feature Hardlink Symlink
Points to inode path
Cross-filesystem no yes
Breaks if target removed no yes
Separate inode no yes
Common use duplicate refs shortcuts

Mental model

Symlink:

  • shortcut/path reference

Hardlink:

  • another name for the same file

Variables & Environment

Pattern Meaning Example
v=hi assign name="tien"
export v export variable export PATH
readonly v constant readonly API_KEY
local v=x function-local inside functions
$? last exit code echo $?

Example

API_KEY="abc"

export API_KEY

python app.py

Child processes only inherit exported variables.


IFS, Reading & Arrays (Bash)

IFS

Pattern Meaning
IFS word splitting chars
IFS=$'\n' newline-only split
IFS=: colon split
IFS= read -r line safe raw line read

Arrays

Pattern Meaning Example
a=(x y z) create array a=(1 2 3)
declare -a a declare array declare -a files
${a[0]} first element ${a[0]}
${a[@]} all elements "${a[@]}"
${#a[@]} array length ${#a[@]}
a+=(x) append a+=(4)
read -ra a split input into array read -ra a <<< "$s"
readarray -t a read lines into array readarray -t a < file.txt
mapfile -t a same as readarray mapfile -t a < file.txt

Why IFS= read -r matters

while IFS= read -r line; do
  echo "$line"
done < file.txt
  • IFS= prevents trimming/splitting
  • -r prevents backslash escaping
  • safest way to read raw lines

Safe iteration

for f in "${files[@]}"; do
  echo "$f"
done

Always quote "${arr[@]}".

Unquoted arrays can split on spaces unexpectedly.

Read file into array

readarray -t lines < file.txt

printf '%s\n' "${lines[@]}"

Common Internal Variables

Variable Meaning
$0 script name
$1..$9 positional args
$# arg count
"$@" safe all args
$* unsafe all args
$$ shell PID
$! last bg PID
$? last exit code
$PWD current dir
$OLDPWD previous dir
$RANDOM random int
$LINENO current line
${BASH_SOURCE[0]} script path

Notes

Always prefer:

"$@"

over:

$*

because it preserves argument boundaries safely.


Functions

Pattern Meaning Example
f() {} function f(){ echo ok; }
function f {} Bash alternative non-POSIX
$1 "$@" function args echo "$1"
return n function exit code return 42

Return vs output

This does NOT return a string:

return "hello"

return only supports numeric exit codes.

Use stdout instead:

get_name() {
  echo "Tien"
}

Conditionals & Comparisons

if / elif / else

if [[ $x -gt 10 ]]; then
  echo big
elif [[ $x -gt 5 ]]; then
  echo medium
else
  echo small
fi

case

Usually cleaner than long if-chains.

case "$cmd" in
  start) echo "Starting" ;;
  stop) echo "Stopping" ;;
  *) echo "Unknown" ;;
esac

Pattern matching

[[ "$file" == *.txt ]]

Regex

[[ "$x" =~ ^[0-9]+$ ]]

Regex works only inside [[ ]].

Numeric Operators

Operator Meaning
-eq equal
-ne not equal
-gt greater
-lt less
-ge greater/equal
-le less/equal

Examples:

(( a > b ))

[ "$a" -gt "$b" ]

File Tests

Pattern Meaning
-f regular file
-d directory
-e exists
-s size > 0
-r -w -x read/write/exec
-n non-empty string
-z empty string

Example:

[[ -f "$path" ]]

Loops

Pattern Meaning Example
for x in ... iterate list for f in *.txt; do ...; done
while cmd loop while success while read -r l; do ...; done
until cmd loop until success until ping -c1 host; do :; done
break / continue flow control loop control

Example

for f in *.txt; do
  echo "$f"
done

Always quote loop variables.


Arithmetic

Pattern Meaning
((i++)) increment
((i+=2)) add
x=$((a+b)) compute

Example:

count=0

((count+=1))

echo "$count"

find

Command Meaning
find . -name "*.txt" find by name
find . -type f files only
find . -type d directories only
find . -maxdepth 2 limit recursion
find . -mtime -1 modified <1 day
find . -size +100M larger than 100 MB
find . -exec cmd {} \; run per file

Examples

find . -name "*.log"

find . -type f -exec rm {} \;

Safe filename handling

Prefer:

find . -print0 | xargs -0

This safely handles:

  • spaces
  • tabs
  • newlines in filenames

xargs

Command Meaning
xargs cmd stdin -> args
xargs -0 null-safe mode
xargs -n1 one arg per cmd
xargs -P4 parallel jobs
xargs -I{} placeholder substitution
xargs bash -c run shell snippet

Parallel compression

find . -name "*.fastq" -print0 \
  | xargs -0 -P8 -I{} gzip "{}"

Runs 8 compression jobs in parallel.

Process many files safely

find data -name "*.txt" -print0 \
  | xargs -0 -P4 -I{} bash -c '
      wc -l "{}"
    '
  • find locates files
  • -print0 handles spaces safely
  • xargs -0 consumes safely
  • -P4 parallelizes work

Script Safety Flags

set -euo pipefail
Flag Meaning
-e exit on command failure
-u error on unset variables
pipefail pipeline fails if any command fails

Why pipefail matters

Without pipefail:

false | true

Pipeline exits successfully because the last command succeeded.

With:

set -o pipefail

the pipeline fails correctly.

Notes

  • set -e has edge cases and is not perfect error handling.
  • Commands inside if, while, &&, || may behave differently.
  • pipefail prevents hidden failures in pipelines.
  • Combine with traps for easier debugging.

Example:

set -Eeuo pipefail

trap 'echo "error at line $LINENO"' ERR