Bash scripting: 50 cards on quoting, tests, loops and error handling
Bash scripting flashcards: shebangs, quoting, exit codes, tests, loops, functions, arrays, parameter expansion, redirection, set -euo pipefail and traps.
Practice now
Study without an account
#!/usr/bin/env bash on the first line do?Scheduled with the default Askesis timings. Progress is saved in this browser.
All 50 cards
- 1What does
#!/usr/bin/env bashon the first line do?This shebang tells the kernel which interpreter runs the script;envfindsbashon thePATH, which is more portable than a fixed#!/bin/bashIt only applies when you run the file directly (./script.sh), which also needs execute permission. - 2
#!/bin/shvs#!/usr/bin/env bashshis a POSIX shell (dash on Debian and Ubuntu) without Bash features such as[[ ]], arrays and${var//a/b}; use a Bash shebang whenever a script relies on them - 3Running
./script.shvssource script.shRunning starts a child process, so its variables andcdvanish when it ends;source(or.) runs the file in the current shell, so its changes persistSource files that set environment variables or define functions for your current shell. - 4What’s wrong with
name = "Ada"?The spaces make Bash run a command callednamewith the arguments=andAda. Assignments take no spaces:name="Ada" - 5Single vs double quotesSingle quotes keep everything literal; double quotes still expand
$var,$(command)and$((arithmetic))but prevent word splitting and globbingbashname="Ada" echo 'Hi $name' # Hi $name echo "Hi $name" # Hi Ada - 6Why quote variables, as in
rm "$file"?Unquoted expansions are split on whitespace and glob-expanded: withfile="my report.txt",rm $filetries to deletemyandreport.txtQuote every expansion unless you specifically want splitting. ShellCheck flags missing quotes. - 7Store a command’s output in a variablebash
today=$(date +%F) lines=$(wc -l < access.log)Prefer$(...)to backticks: it nests and reads more clearly. Trailing newlines are stripped. - 8Integer arithmetic in Bashbash
total=$(( price * qty )) (( count++ )) echo $(( 7 / 2 )) # 3Bash arithmetic is integer-only; usebcorawkfor decimals. - 9
$0,$1and$#The script’s name, its first argument, and the number of argumentsFrom the tenth argument on, use braces:${10}. - 10
"$@"vs"$*""$@"expands to each argument as a separate word, keeping spaces inside arguments intact;"$*"joins them all into one stringTo pass arguments on to another command, you almost always want"$@". - 11What does
shiftdo?Drops$1, moves the remaining arguments down by one ($2becomes$1) and decreases$#shift 2drops two. It is common in argument-parsing loops. - 12Parse short options such as
-vand-f filebashwhile getopts "vf:" opt; do case $opt in v) verbose=1 ;; f) file="$OPTARG" ;; *) usage; exit 2 ;; esac done shift $((OPTIND - 1))A colon after a letter means the option takes a value.getoptsonly handles short options. - 13Exit with a usage message when the first argument is missingbash
env="${1:?usage: deploy.sh <env>}"${var:?message}prints the message to stderr and exits with a non-zero status if the variable is unset or empty. - 14Exit status conventions0 means success and 1–255 failure; 2 often signals misuse, 126 a command that isn’t executable, 127 command not found, and 128 + N death by signal N (130 after Ctrl+C)A script exits with
exit N, or else with the status of its last command. - 15Why save
$?right away?Every command, evenechoor[, overwrites it, so store it immediately:cmd; status=$?Often you don’t need$?at all: test the command directly withif. - 16Run a block only if
grepfinds a matchbashif grep -q "ERROR" app.log; then echo "errors found" fiiftests a command’s exit status, so there is no need for[ $? -eq 0 ]. - 17Fail early when a required tool is missingbash
command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; }command -vis built in and portable, unlikewhich. - 18
[ ]vs[[ ]][is an ordinary command (POSIXtest), so unquoted variables inside it are split and globbed.[[ ]]is Bash syntax: no splitting, plus&&,||, glob patterns and regex matching with=~ - 19Why is
[[ 10 < 9 ]]true?Inside[[ ]],<and>compare strings alphabetically, and “10” sorts before “9”. For numbers use-lt,-gtand friends, or(( 10 < 9 ))The numeric operators are-eq,-ne,-lt,-le,-gtand-ge. - 20Common file tests
-eexists,-fregular file,-ddirectory,-snon-empty file, and-r,-w,-xreadable, writable, executablebash[[ -f .env ]] && source .env - 21
-zvs-n[[ -z $var ]]is true when the string is empty;[[ -n $var ]]is true when it is not - 22Match a regex and extract its groupsbash
re='^([0-9]+)\.([0-9]+)$' if [[ $version =~ $re ]]; then major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} fiKeep the regex in a variable and don’t quote it after=~: a quoted pattern is matched as a literal string. - 23Branch on a value with
casebashcase "$1" in start) start_app ;; stop) stop_app ;; -h|--help) usage ;; *) echo "unknown command: $1" >&2; exit 2 ;; esacPatterns are globs,|separates alternatives and*)is the default branch. - 24Loop over all
.logfiles in a directorybashfor f in *.log; do gzip "$f" doneIf nothing matches, the loop runs once with the literal string*.log;shopt -s nullglobmakes it run zero times instead. - 25Count from 1 to n when n is a variablebash
for (( i = 1; i <= n; i++ )); do echo "$i" doneBrace expansion like{1..5}happens before variables are expanded, so{1..$n}doesn’t work. - 26Read a file line by linebash
while IFS= read -r line; do printf '%s\n' "$line" done < input.txtIFS=keeps leading and trailing whitespace and-rkeeps backslashes. Don’t loop over$(cat file). - 27A counter set inside
cmd | while read …is 0 afterwards. Why?Each part of a pipeline runs in a subshell, so the loop’s variables disappear with it. Feed the loop with process substitution instead:done < <(cmd)Alternatively,shopt -s lastpiperuns the last part of a pipeline in the current shell (in scripts, where job control is off). - 28Wait until a service is healthybash
until curl -fsS http://localhost:8080/health >/dev/null; do sleep 2 doneuntilloops as long as the command fails. Add a retry limit in real scripts. - 29Run two jobs in parallel and wait for bothbash
build_frontend & build_backend & waitPlainwaitreturns 0 even if a job failed. Save each PID from$!and runwait "$pid"to get that job’s exit status. - 30Define a function with a local variablebash
greet() { local name="$1" echo "Hello, $name" } greet "Ada"Withoutlocal, variables set in a function are global. Inside a function,$1,$2and$@are the function’s own arguments. - 31How does a Bash function return data?It prints the data and the caller captures it:
result=$(get_version).returnonly sets an exit status from 0 to 255Larger values wrap around:return 300gives a status of 44. - 32Indexed arraysbash
files=(a.txt "b c.txt") files+=(d.txt) echo "${files[0]}" # a.txt echo "${#files[@]}" # 3 for f in "${files[@]}"; do echo "$f"; doneQuote"${files[@]}"so elements containing spaces stay whole. - 33Associative arraysbash
declare -A port=([web]=80 [db]=5432) echo "${port[db]}" # 5432 for name in "${!port[@]}"; do echo "$name"; doneThey need Bash 4 or newer; macOS still ships Bash 3.2 as/bin/bash. - 34
${var:-default}vs${var:=default}Both givedefaultwhenvaris unset or empty;:=also assigns it tovarWithout the colon, as in${var-default}, only an unset variable triggers the default, not an empty one. - 35Get the file name, directory and extension from a pathbash
path=/var/log/app/error.log echo "${path##*/}" # error.log echo "${path%/*}" # /var/log/app echo "${path%.*}" # /var/log/app/error echo "${path##*.}" # log#removes the shortest matching prefix and##the longest;%and%%do the same for suffixes. - 36Replace text inside a variable
${var/old/new}replaces the first match;${var//old/new}replaces all of thembashf="annual report.txt" echo "${f// /_}" # annual_report.txt - 37String length and substrings
${#var}is the length;${var:offset:length}extracts a substring, as in${commit:0:7} - 38Print an error message to stderrbash
echo "config file not found" >&2Keep stdout for real output, soscript | other-toolisn’t polluted by messages. - 39
cmd > out.log 2>&1vscmd 2>&1 > out.logRedirections apply left to right. The first sends both streams to the file; the second points stderr at the terminal (where stdout was at that moment) and only then moves stdout to the fileTo pipe errors along with output:cmd 2>&1 | grep -i error. - 40What does
&>do?Redirects both stdout and stderr, socmd &> /dev/nullsilences a command completelyIt is Bash shorthand for> file 2>&1;&>>appends. - 41Write a multi-line file with a here-documentbash
cat > app.conf <<EOF host=$HOST port=8080 EOFVariables expand inside it. Quote the delimiter (<<'EOF') to keep the text literal, or use<<-EOFto strip leading tabs. - 42Compare the sorted contents of two files without temp filesbash
diff <(sort a.txt) <(sort b.txt)Process substitution,<(cmd), exposes a command’s output as a file path. - 43Send everything a script prints to a log file and the screenbash
exec > >(tee -a deploy.log) 2>&1Placed near the top, it applies to all later output of the script. - 44What does
set -euo pipefaildo?-eexits on the first failing command,-umakes expanding an unset variable an error, andpipefailmakes a pipeline fail if any command in it failsA common first line after the shebang in scripts that should stop on errors. - 45When doesn’t
set -estop a script on a failing command?When the command is anif,whileoruntilcondition, part of an&&or||list (except the last), negated with!, or masked bylocal, as inlocal out=$(cmd)Declare and assign separately so failures count:local out; out=$(cmd). - 46Without
pipefail, what is the status offalse | true?0, because a pipeline returns the status of its last command. Withset -o pipefailit returns the status of the rightmost command that failed${PIPESTATUS[@]}lists the status of every command in the last pipeline. - 47Always delete a temp file when the script exitsbash
tmp=$(mktemp) trap 'rm -f "$tmp"' EXITTheEXITtrap runs on a normal end, anexit, aset -efailure or Ctrl+C. Nothing can trapkill -9(SIGKILL). - 48Report the line number where a script failedbash
trap 'echo "Error on line $LINENO" >&2' ERRSingle quotes delay expansion until the trap runs. Addset -Eso functions inherit the trap. - 49Trace a script while it runsbash
bash -x deploy.sh # trace the whole script set -x # trace from here on set +x # stop tracingEach command is printed after expansion, prefixed with+. ShellCheck catches many quoting and logic bugs before you run anything. - 50Get the directory the script lives inbash
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"Use it to load files that sit next to the script, wherever it is run from.
Frequently asked questions
How is this different from the Linux and Git & terminal decks?
Those cover commands such as find, grep, pipes and permissions. This deck is about the Bash language itself: variables, quoting, control flow, functions and error handling in scripts.
Will these scripts work in sh or zsh?
Not always. Features such as [[ ]], arrays, pattern substitution in variables and process substitution are Bash-specific, so use a Bash shebang or stick to POSIX features when a script must run under sh.
Does it work on macOS?
Mostly. macOS ships the old Bash 3.2 and uses zsh as its default shell; associative arrays and a few other features need Bash 4 or newer, which you can install with Homebrew.
More in IT & DevOps
Linux command line
Linux commands and files for sysadmins and LPIC-1 candidates: find and grep, permissions and special bits, fstab, systemd targets, package managers, users, cron and more.
50 cardsLinux filesystem basics
Learn the Linux directory tree: what /etc, /var, /usr, /proc and /dev are for, plus passwd, shadow, fstab and hosts. Free flashcards, no signup.
50 cardsIntermediate Linux filesystem
Linux paths admins use daily: /usr/local, /var/lib, /run, /sys, /etc/skel, sudoers, Bash startup files, SSH keys, resolv.conf and /proc. Free flashcards.
50 cardsAdvanced Linux filesystem
Deeper Linux files for LPIC-1: profile.d, sudoers.d, systemd units, GRUB config, nsswitch.conf, ld.so.conf, distro logs and /lib/modules. Free flashcards.
50 cardsVim
Learn Vim from the keyboard up: modes, motions, operators and text objects, search and :s substitutions, registers, macros, visual mode and windows.
50 cardsNetworking
Computer networking fundamentals: OSI and TCP/IP layers, TCP vs UDP, common port numbers, subnetting and CIDR, DNS, DHCP and ARP, and IPv6 basics.
50 cardsCyber security
Cyber security flashcards: the CIA triad, phishing, XSS, CSRF, SQL injection, MITM, hashing vs encryption, TLS, MFA, least privilege, OWASP and zero trust.
50 cardsDocker
Learn Docker from images and layers to multi-stage builds, volumes, networks and Compose, with the commands and best practices you use daily.
50 cardsKubernetes
Kubernetes fundamentals for developers and CKA or CKAD candidates: Pods, Deployments, Services, storage, probes, resources and scheduling, plus the kubectl commands you use daily.
50 cardsAWS
AWS flashcards: regions and AZs, EC2, S3 storage classes, IAM users, roles and policies, VPC networking, RDS, Lambda, CloudWatch and Route 53.
50 cardsTerraform
Terraform flashcards: HCL, providers, resources, variables and outputs, state and remote backends, plan and apply, modules, count, for_each, drift and import.
50 cards