/user/kayd @ devops :~$ cat bash-basics.md

Bash Basics Cheatsheet Bash Basics Cheatsheet

QR Code linking to: Bash Basics Cheatsheet
Karandeep Singh
Karandeep Singh
• 6 minutes

Summary

A dense quick-reference for Bash 5.x scripting. Quoting, parameter expansion, test operators, loops, functions, arrays, traps, and here-docs.

I write Bash almost every day (deploy scripts, CI glue, one-off automation) and I still forget the exact parameter expansion syntax more often than I’d like to admit. This is the page I keep open: the Bash 5.x bits I actually use, condensed. For text-wrangling inside scripts, see my Bash sed & awk Cheatsheet.

Shebang & Safe Defaults

Docs: The Set Builtin, GNU Bash manual

Start every script with this:

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
OptionEffect
set -eExit immediately on any command failure
set -uError on unset variables (catches typos)
set -o pipefailA pipeline fails if any stage fails, not just the last
set -xPrint each command before running (debug mode)
bash -x script.sh          # debug an existing script without editing it
set +e                     # temporarily allow failures
some_flaky_command || true # tolerate a single failing command under set -e

Variables & Quoting

Docs: Quoting, GNU Bash manual

name="karandeep"           # no spaces around =
readonly REGION="ca-central-1"
export PATH="$PATH:/opt/bin"
unset name

echo "$name"               # double quotes expand variables; use by default
echo '$name'               # single quotes: literal, no expansion
echo "${name}_backup"      # braces to delimit the variable name

Parameter Expansion

Docs: Shell Parameter Expansion, GNU Bash manual

ExpansionMeaning
${var:-default}Use default if var is unset or empty
${var:=default}Assign default to var if unset or empty
${var:?message}Exit with message if var is unset or empty
${var:+alt}Use alt only if var IS set
${#var}Length of var
${var#pattern}Remove shortest match from the front
${var##pattern}Remove longest match from the front
${var%pattern}Remove shortest match from the end
${var%%pattern}Remove longest match from the end
${var/a/b}Replace first a with b
${var//a/b}Replace all a with b
${var^^} / ${var,,}Uppercase / lowercase
${var:2:5}Substring: offset 2, length 5
file="app.tar.gz"
echo "${file%%.*}"     # app          (strip longest from end-matching .*)
echo "${file%.*}"      # app.tar      (strip shortest)
echo "${file#*.}"      # tar.gz
path="/var/log/nginx/access.log"
echo "${path##*/}"     # access.log   (basename)
echo "${path%/*}"      # /var/log/nginx (dirname)
env="${DEPLOY_ENV:?DEPLOY_ENV must be set}"

Conditionals & [[ ]] Test Operators

Docs: Bash Conditional Expressions, GNU Bash manual

if [[ -f "$config" && "$env" == "prod" ]]; then
  echo "prod config found"
elif [[ "$env" =~ ^(dev|stage)$ ]]; then
  echo "non-prod"
else
  echo "unknown env" >&2
  exit 1
fi
OperatorTrue if…
-f fileRegular file exists
-d dirDirectory exists
-e pathPath exists (any type)
-r / -w / -xReadable / writable / executable
-s fileFile exists and is non-empty
-z strString is empty
-n strString is non-empty
str1 == str2Strings equal (glob match if unquoted RHS)
str =~ regexRegex match (capture groups in BASH_REMATCH)
n1 -eq n2Integers equal (-ne -lt -le -gt -ge)
f1 -nt f2f1 newer than f2

Use [[ ]] over [ ]: no word splitting, and it supports &&, ||, =~, and glob matching.

Loops

Docs: Looping Constructs, GNU Bash manual

for f in /etc/*.conf; do echo "$f"; done          # glob loop
for i in {1..5}; do echo "attempt $i"; done       # brace range
for ((i = 0; i < 10; i++)); do echo "$i"; done    # C-style

while read -r line; do                            # read a file safely
  echo "line: $line"
done < servers.txt

until curl -sf http://localhost:8080/health; do   # wait for a service
  sleep 2
done

while true; do date; sleep 5; done                # poll loop
break; continue                                   # work as expected

Functions

Docs: Shell Functions, GNU Bash manual

deploy() {
  local env="$1"                  # local keeps vars scoped
  local version="${2:-latest}"    # default for optional args
  echo "deploying $version to $env"
  return 0                        # numeric status only; echo for data
}

deploy prod v1.2.3
result="$(deploy stage)"          # capture output via command substitution

$1..$9 ${10} positional args, $# count, $@ all args (quote it: "$@"), $0 script name, $FUNCNAME current function.

Arrays & Associative Arrays

Docs: Arrays, GNU Bash manual

hosts=("web1" "web2" "db1")
hosts+=("cache1")                 # append
echo "${hosts[0]}"                # first element
echo "${hosts[@]}"                # all elements
echo "${#hosts[@]}"               # length
echo "${hosts[@]:1:2}"            # slice: web2 db1
for h in "${hosts[@]}"; do ssh "$h" uptime; done

declare -A ports=([http]=80 [https]=443 [ssh]=22)
ports[dns]=53
echo "${ports[https]}"            # 443
for k in "${!ports[@]}"; do echo "$k -> ${ports[$k]}"; done

mapfile -t lines < servers.txt    # file into array, one line per element

Exit Codes

Docs: Exit Status, GNU Bash manual

somecommand
echo "$?"                         # status of last command: 0 = success

grep -q "ERROR" app.log && echo "errors found"
systemctl is-active nginx || systemctl start nginx
command -v jq >/dev/null || { echo "jq required" >&2; exit 127; }
CodeConvention
0Success
1General error
2Misuse of builtin / bad usage
126Found but not executable
127Command not found
130Killed by Ctrl-C (SIGINT)

Traps

Docs: Bourne Shell Builtins (trap), GNU Bash manual

tmpdir="$(mktemp -d)"
cleanup() { rm -rf "$tmpdir"; }
trap cleanup EXIT                          # runs on any exit, even set -e

trap 'echo "failed at line $LINENO" >&2' ERR
trap 'echo "interrupted"; exit 130' INT TERM

EXIT is the workhorse: temp files, lock files, and spawned background jobs all get cleaned up no matter how the script dies.

Here-Docs & Here-Strings

Docs: Redirections, GNU Bash manual

cat <<EOF > /etc/myapp/config.yaml
env: $DEPLOY_ENV
region: ca-central-1
EOF

cat <<'EOF'                       # quoted delimiter: NO expansion
literal $HOME stays as-is
EOF

sudo tee /etc/motd <<EOF >/dev/null
Managed by automation. Do not edit.
EOF

grep "prod" <<< "$config_text"    # here-string: feed a variable to stdin

Command Substitution & Arithmetic

Docs: Shell Arithmetic, GNU Bash manual

now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"       # prefer $() over backticks
files="$(find /var/log -name '*.gz' | wc -l)"

(( count = files + 1 ))                     # arithmetic context
echo $(( 7 / 2 ))                           # 3 (integer division only)
(( count > 100 )) && echo "log buildup"
((i++)); ((total += batch))
echo $(( RANDOM % 6 + 1 ))                  # dice roll

awk 'BEGIN { printf "%.2f\n", 7/2 }'        # need floats? use awk or bc

Everything here compounds with the interactive tricks in the Bash CLI One-Liners Cheatsheet. The loop you prototype at the prompt becomes the script you commit.

0
  • Bash sed & awk: substitution, address ranges, capture groups, and awk field handling for when plain Bash string manipulation runs out of road
  • Bash CLI One-Liners: find/xargs, grep, jq, rsync, and process-triage pipelines worth running interactively before they ever become scripts
  • Go (Golang): a different escape hatch. When a script needs real types, tests, and a single static binary, port it to Go

More Bash pages live in the Bash group, and everything else is in the cheatsheet library.

Similar Articles

More from development

Go (Golang) Cheatsheet

Go cheatsheet covering the go toolchain, syntax essentials, goroutines and channels, table-driven …

tmux Cheatsheet

Quick-reference tmux cheatsheet: sessions, windows, panes, copy mode, synchronize-panes, .tmux.conf …

Jenkins Cheatsheet

Jenkins cheatsheet with declarative pipeline syntax, credentials, parameters, parallel stages, …

Bash sed & awk Cheatsheet

Practical sed and awk cheatsheet: substitution, addresses, capture groups, awk fields, BEGIN/END …