____ _ | __ ) __ _ ___| |__ | _ \ / _` / __| '_ \ | |_) | (_| \__ \ | | | |____/ \__,_|___/_| |_|

Bash

Bourne Again SHell — the standard Unix scripting language

GNU Bash 5.x · POSIX compliant · 35+ years strong
Variables & Strings
Variables
NAME="Alice" # assign (no spaces!) echo "Hello $NAME" # expand echo "Hello ${NAME}" # braces: safer unset NAME # delete readonly PI=3.14 # constant export PATH # pass to child procs
String Operations
s="hello world" echo ${#s} # length: 11 echo ${s:6} # slice: "world" echo ${s:0:5} # "hello" echo ${s/hello/hi} # replace first echo ${s^^} # HELLO WORLD echo ${s,,} # lowercase
Default Values
${VAR:-default} # use default if unset/null ${VAR:=default} # assign default if unset ${VAR:?error} # error if unset ${VAR:+other} # use other if set
Path / Filename Expansion
f="/home/user/script.sh" echo ${f##*/} # script.sh (basename) echo ${f%/*} # /home/user (dirname) echo ${f##*.} # sh (extension) echo ${f%.*} # strip extension
Arrays
Indexed Arrays
arr=("a" "b" "c") # declare echo ${arr[0]} # a echo ${arr[@]} # all elements echo ${#arr[@]} # length: 3 arr+=("d") # append unset 'arr[1]' # delete element
Associative Arrays
declare -A map map["key"]="value" map["host"]="localhost" echo ${map["key"]} # value echo ${!map[@]} # all keys echo ${map[@]} # all values
Looping Arrays
for item in "${arr[@]}"; do echo "$item" done # indexed loop for i in "${!arr[@]}"; do echo "$i: ${arr[$i]}" done
Control Flow
if / elif / else
if [[ "$x" == "yes" ]]; then echo "yes" elif [[ $x -gt 10 ]]; then echo "big" else echo "other" fi
Test Operators
# strings [[ "$a" == "$b" ]] [[ "$a" != "$b" ]] [[ -z "$a" ]] # empty string [[ -n "$a" ]] # non-empty # numbers [[ $n -eq 5 ]] [[ $n -lt 10 ]] [[ -f "$f" ]] # is file [[ -d "$d" ]] # is dir [[ -x "$f" ]] # executable
Loops
# C-style for for (( i=0; i<10; i++ )); do echo $i done # while loop while read -r line; do echo "$line" done < file.txt
case Statement
case "$opt" in start) echo "starting" ;; stop) echo "stopping" ;; re*) echo "restart*" ;; -v|--verbose) VERBOSE=1 ;; *) echo "unknown" ;; esac
Functions
Defining & Calling
greet() { local name="$1" echo "Hello, $name!" return 0 } greet "World" echo "Exit: $?"
Returning Values
# capture stdout get_date() { date +"%Y-%m-%d"; } TODAY=$(get_date) # nameref (bash 4.3+) add() { local -n result=$1 result=$(( $2 + $3 )) } add SUM 3 4; echo $SUM # 7
Special Parameters
$0 # script name $1..$9 # positional args $# # number of args $@ # all args (array-safe) $? # last exit code $$ # current PID $! # last background PID $- # current shell options
I/O & Redirection
Redirects
cmd > file # stdout → file (overwrite) cmd >> file # stdout append cmd 2> err # stderr → file cmd 2>&1 # stderr → stdout cmd &> file # both → file cmd < file # stdin from file cmd << EOF # here-doc multi line EOF cmd <<< "str" # here-string
Pipes & Process Sub
cmd1 | cmd2 # pipe stdout cmd1 |& cmd2 # pipe stdout+stderr # process substitution diff <(sort a) <(sort b) tee >(gzip > out.gz) # subshell capture result=$(echo "hello" | tr a-z A-Z)
printf
printf "%-10s %5d\n" "Name" 42 printf "%08.2f\n" 3.14 printf "%b" "line\n" # interpret \n printf "%q" "a b" # shell-escape
Arithmetic
Integer Math
(( x = 5 + 3 )) # 8 (( x++ )) # post-increment (( x **= 2 )) # power echo $(( 17 % 5 )) # modulo: 2 echo $(( 0xFF )) # hex: 255 echo $(( 2#1010 )) # binary: 10
let & bc
let "x = 5 * 3" echo $x # 15 # floating point with bc echo "scale=4; 22/7" | bc echo "sqrt(2)" | bc -l
Useful Patterns
Error Handling
set -euo pipefail # strict mode # -e: exit on error # -u: error on unset vars # -o pipefail: pipe errors count trap 'echo "ERR on line $LINENO"' ERR trap 'cleanup' EXIT # always runs
Script Template
#!/usr/bin/env bash set -euo pipefail DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) usage() { echo "Usage: $0 <arg>"; exit 1; } [[ $# -lt 1 ]] && usage main() { echo "arg=$1 dir=$DIR"; } main "$@"
getopts (Arg Parsing)
while getopts ":hv:o:" opt; do case "$opt" in h) usage ;; v) VERBOSE="$OPTARG" ;; o) OUT="$OPTARG" ;; \?) echo "Bad opt"; usage ;; esac done shift $(( OPTIND - 1 ))
Common Idioms
# check command exists command -v docker &>/dev/null || exit 1 # run if not root [[ $EUID -ne 0 ]] && exec sudo "$0" "$@" # temp file with cleanup TMP=$(mktemp) trap "rm -f $TMP" EXIT
Special Operators
OperatorMeaningExample
&&AND — run if previous succeedsmake && make install
||OR — run if previous failscd /tmp || exit 1
;Sequence — always run nextcd /; ls
&Background — run asyncsleep 10 &
|Pipe stdout to next stdincat f | grep x
$()Command substitutionNOW=$(date)
$(())Arithmetic expansionecho $((2**10))
{}Brace expansionecho {a,b,c}.txt
~Home directorycd ~/projects
!Negate exit statusif ! grep -q x f; then
Timeline
1971
Thompson shell (sh) — Ken Thompson writes the first Unix shell for Unix V1 at Bell Labs. It introduces pipes and simple I/O redirection, the core ideas bash inherits.
1979
Bourne Shell (sh)sh — Stephen Bourne rewrites the shell for Unix V7 at Bell Labs. Introduces here-docs, variables, functions, and structured control flow. Still the POSIX baseline.
1983
IEEE POSIX work begins — The 1003.2 committee starts standardizing shell behavior, ensuring portability across Unix variants. Bourne shell becomes the de facto reference.
1987
Brian Fox starts bash — The Free Software Foundation assigns Brian Fox to write a free, open-source replacement for sh. The name "Bourne Again SHell" is a nod to both its origin and the FSF's ethos.
1989
Bash 1.0 releasedv1.0 — January 10, 1989. Ships with GNU/Linux from the start. Adds readline for command editing, job control, and history. Immediately becomes the default shell on Linux.
1993
Chet Ramey takes over — Brian Fox steps back; Chet Ramey at Case Western Reserve University becomes the primary maintainer. He still maintains bash today — over 30 years of stewardship.
1996
Bash 2.0v2.0 — Indexed arrays, $FUNCNAME, ${!var} indirect expansion, improved getopts, and programmable completion framework. Bash diverges meaningfully from sh.
2004
Bash 3.0v3.0 — Regex matching with [[ $x =~ pattern ]], BASH_REMATCH capture groups. Apple ships bash 3.x on macOS (it stays there until 2019 due to licensing).
2009
Bash 4.0v4.0 — Associative arrays (declare -A), ** globstar recursion, coprocesses (coproc), mapfile/readarray. Substantial language expansion.
2014
ShellShockCVE-2014-6271 — Critical RCE vulnerability: bash executes trailing code after function definitions in environment variables. Affects web servers, DHCP clients, SSH, and more. Patched within 24 hours; additional variants patched over following weeks.
2016
Bash 4.4v4.4printf -v improvements, wait -n (wait for any job), ${!name[@]} nameref fix, @Q/@E parameter transformation operators.
2019
Bash 5.0v5.0$EPOCHSECONDS, $EPOCHREALTIME, $BASH_ARGV0, improved associative array handling, history timestamps, and over 70 bug fixes.
2019
macOS switches to zshmacOS Catalina — Apple changes the default login shell from bash 3.2 to zsh. Reason: bash 4+ uses GPL v3 which Apple won't ship; bash 3.2 stays available but deprecated. Bash remains the scripting standard on Linux.
2022
Bash 5.2v5.2${array@K} key-value output, better error messages for common mistakes, noexpand_translation, new GLOBIGNORE patterns. Bug fixes from 5.1.
Why Bash?
🌐
Ubiquity
Pre-installed on virtually every Linux distribution and available on macOS. Write a bash script and it runs on your laptop, a cloud VM, a container, or a supercomputer without installing anything.
No Runtime
No virtual machine, no interpreter package, no dependency tree. Bash is a single binary. Your script starts instantly — no JVM warm-up, no package manager, no pip install.
🔧
DevOps Standard
GitHub Actions, GitLab CI, Jenkins, CircleCI, and Kubernetes all default to bash for shell steps. Every Dockerfile RUN layer is bash. It's the lingua franca of automation pipelines.
🔗
Unix Philosophy
Bash is the glue between tools. Pipe grep into awk into sort into uniq — each program does one thing well, and bash connects them. Text streams as a universal interface.
⚙️
Process Control
Job control, background tasks (&), wait, trap for signals, process substitution, coprocesses. Bash can orchestrate complex multi-process workflows natively.
📝
Text Processing
Direct integration with grep, sed, awk, cut, tr, sort, uniq. Parse log files, transform CSVs, extract fields — without launching Python or installing libraries.
🚀
Rapid Automation
Turn a one-off sequence of commands into a repeatable script in minutes. Cron it. Add error handling with set -euo pipefail. Zero boilerplate to get something running.
📦
Portability
A bash 4+ script runs identically on Ubuntu, CentOS, Debian, Alpine, and any POSIX Linux. Targeting bash 3.2 even covers macOS. Write once, run everywhere Linux exists.
Bash vs. Other Scripting Languages
Capability Bash Python Node.js
Pre-installed on Linux ✓ Always Often (varies) Rarely
Startup time ~10ms ~50-100ms ~100-200ms
Pipe / process integration Native subprocess module child_process module
Docker / CI default ✓ Yes If installed If installed
Complex data structures Limited ✓ Rich ✓ Rich
Readability at scale Degrades ✓ Excellent ✓ Good
bash — ~/projects
(reverse-i-search)` ':
user@myhost:~/projects$
Escape Sequences
SequenceExpands toExample output
\uCurrent usernamealice
\hHostname up to first dotmyhost
\HFull hostnamemyhost.local
\wCurrent working directory (~ abbreviated)~/projects/app
\WBasename of current directoryapp
\$$ for normal user, # for root$
\nNewline — start a second prompt line(newline)
\tCurrent time in HH:MM:SS (24h)14:32:07
\TCurrent time in HH:MM:SS (12h)02:32:07
\@Current time in 12h am/pm02:32 PM
\dDate as "Weekday Month Date"Thu Apr 3
\!History number of this command142
\#Command number in this session17
\jNumber of active jobs2
\sName of the shellbash
\vBash version (major.minor)5.2
\VBash version (full release)5.2.15
\[ \]Wrap non-printing chars (required for colors)\[\e[32m\]
ANSI Color Codes
Color Syntax
# format: \[\e[CODEm\] or \[\033[CODEm\] # reset: \[\e[0m\] PS1='\[\e[1;32m\]\u\[\e[0m\]@\h:\w\$ ' # ↑ bold green ↑ reset # multiple attrs: bold + color PS1='\[\e[1;36m\]\u\[\e[0m\]' # 1=bold, 36=cyan
Code Reference
# Attributes 0 reset 1 bold 2 dim 4 underline 5 blink 7 reverse # Foreground colors 30 black 31 red 32 green 33 yellow 34 blue 35 magenta 36 cyan 37 white 90-97 bright # Background colors: add 10 40 black 41 red 42 green 44 blue 46 cyan 47 white
PS1 Gallery
Dynamic Prompts & PROMPT_COMMAND
Exit Code in Prompt
# show ✓ or ✗ based on last exit code __status() { if [[ $? -eq 0 ]]; then echo '\[\e[32m\]✓\[\e[0m\]' else echo '\[\e[31m\]✗\[\e[0m\]' fi } PS1='$(__status) \w\$ '
PROMPT_COMMAND
# runs before each prompt is displayed PROMPT_COMMAND='history -a' # appends history to file immediately # multiple commands PROMPT_COMMAND='history -a; __set_title' # set terminal title to current dir __set_title() { printf '\e]2;%s\a' "bash: $PWD" }
PS2, PS3, PS4
# PS2: continuation prompt (default: "> ") PS2='\[\e[33m\]→ \[\e[0m\]' # PS3: select statement menu prompt PS3="Choose: " # PS4: set -x trace prefix (default: "+ ") PS4='\[\e[35m\]+${BASH_SOURCE}:${LINENO}: \[\e[0m\]'