Skip to content

~/avetyan.com/cheatsheets

Cheat sheets

Bash scripting cheat sheet

Write Bash scripts that fail loudly and clean up after themselves: strict mode, quoting, tests, loops, arrays, functions, traps and getopts.

4 min read


Script header

bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
SettingWhat it does

set -e doesn’t trigger inside if, &&, || or a command whose status you test, so check those yourself.

Variables

bash
name="web-1"
echo "$name"                            # always quote expansions
echo "${name}_backup"                   # braces when text follows
today=$(date +%F)                       # command output
readonly CONFIG=/etc/app.conf
export PATH="$HOME/bin:$PATH"
echo "${1:-default}"                    # first argument or a default
echo "$#"                               # number of arguments
echo "$@"                               # all arguments, each kept whole
echo "$?"                               # exit status of the last command
echo "$$"                               # this script's PID

Unquoted $var is split on spaces and expanded as a glob, which is how rm $file deletes the wrong thing.

Tests

TestTrue when
bash
if [[ ! -f "$CONFIG" ]]; then
  echo "missing $CONFIG" >&2
  exit 1
fi

command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }

Loops

bash
for host in web1 web2 web3; do
  ssh "alice@$host.example.com" uptime
done

for f in /var/log/app/*.log; do
  [[ -e "$f" ]] || continue             # no matches: skip the literal pattern
  gzip "$f"
done

for ((i = 1; i <= 5; i++)); do echo "$i"; done

while IFS= read -r line; do
  echo "line: $line"
done < hosts.txt

until curl -fsS http://localhost:8080/health >/dev/null; do sleep 2; done

Arrays

bash
hosts=(web1 web2 web3)
hosts+=(web4)
echo "${hosts[0]}"
echo "${#hosts[@]}"                     # count
for h in "${hosts[@]}"; do echo "$h"; done

declare -A port=([http]=80 [https]=443)
echo "${port[https]}"
for k in "${!port[@]}"; do echo "$k=${port[$k]}"; done

mapfile -t lines < file.txt             # file into an array

Functions

bash
log() { printf '%s %s\n' "$(date +%FT%T)" "$*" >&2; }

backup() {
  local src=$1 dst=$2
  rsync -a "$src" "$dst" || return 1
}

backup /etc /backup/etc || log "backup failed"

Use local for every variable inside a function, or it changes the global one.

Cleanup with trap

bash
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT               # runs on any exit, including errors
trap 'echo "failed at line $LINENO" >&2' ERR
trap 'echo "interrupted"; exit 130' INT TERM

Flags with getopts

bash
usage() { echo "usage: $0 [-v] [-n count] file" >&2; exit 2; }

verbose=0 count=1
while getopts ':vn:h' opt; do
  case $opt in
    v) verbose=1 ;;
    n) count=$OPTARG ;;
    h|*) usage ;;
  esac
done
shift $((OPTIND - 1))
[[ $# -eq 1 ]] || usage
file=$1

Strings and maths

bash
echo $(( 7 * 6 ))
count=$(( count + 1 ))                  # not (( count++ )): it returns 1 when count is 0, and set -e exits
printf '%-10s %5d\n' "$name" "$count"   # aligned output
read -rp "Continue? [y/N] " answer
[[ "$answer" == [yY] ]] || exit 0

Keep in mind

  • Run shellcheck script.sh on every script. It catches most quoting bugs.
  • Send errors to stderr (>&2) and exit non-zero, so cron and CI notice.
  • Past about 200 lines, or once you need real data structures, switch to Python.

Comments

Sign in with Google, GitHub or Facebook to comment.

Loading comments…