Bash Scripting Cheat Sheet (The First Line Every Script Needs)
โก Quick Answer
A practical Bash scripting cheat sheet โ variables, conditionals, loops, functions, and the set -euo pipefail line that turns silent failure into loud failure.
Get more content like this on Telegram!
Daily AI tips, notes & resources โ free
Advertisement
Bash Scripting Cheat Sheet
Most Bash scripts fail silently. A command errors, the script prints something red, and then it carries on to the next line as though nothing happened โ deploying half a build, deleting half a directory, or reporting success on a job that did not run.
One line at the top fixes that, and it is the most important thing on this page.
Updated for 2026. Bash 4+ on Linux, macOS, and WSL.
The First Three Lines
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'| Flag | Effect |
|---|---|
-e | Exit immediately if any command fails |
-u | Error on an undefined variable instead of substituting empty |
-o pipefail | A pipeline fails if any stage fails, not just the last |
Without -u, a typo in a variable name becomes an empty string. rm -rf "$BUILDDIR" where you meant $BUILD_DIR becomes rm -rf "" โ or worse, rm -rf $BUILDDIR/ becomes rm -rf /.
Without -o pipefail, this reports success even though grep found nothing:
grep "ERROR" log.txt | tail -5 # exit status is tail's, which succeededIFS=$'\n\t' removes the space from the field separator, so filenames with spaces stop splitting into multiple arguments during iteration.
#!/usr/bin/env bash, not #!/bin/bash. On macOS, /bin/bash is version 3.2 from 2007 and lacks associative arrays. The env form uses whatever Bash is first on the user's PATH.
Variables
name="Ada" # no spaces around =
count=5
readonly PI=3.14
path="$HOME/projects"
echo "$name" # always quote
echo "${name}_suffix" # braces when the name touches other textQuote every variable
This is not style advice. It is the difference between a working script and a destructive one.
dir="My Documents"
rm -rf $dir # two arguments: "My" and "Documents"
rm -rf "$dir" # one argument, correctAn unquoted variable is subject to word splitting and glob expansion before the command sees it. A value containing a space becomes two arguments; a value containing * expands against the current directory; an empty value disappears entirely.
Defaults and substitution
"${VAR:-default}" # use default if VAR is unset or empty
"${VAR:=default}" # ...and assign it
"${VAR:?message}" # exit with an error if unset โ good for required config
"${#VAR}" # length
"${VAR%.txt}" # strip shortest match from the end
"${VAR##*/}" # strip longest match from the start โ basename
"${VAR/old/new}" # replace first occurrence
"${VAR//old/new}" # replace all"${DATABASE_URL:?DATABASE_URL is required}" is the cleanest way to fail fast on missing configuration, and it fits on one line at the top of a script.
Single vs Double Quotes
name="Ada"
echo "Hello $name" # Hello Ada โ expands
echo 'Hello $name' # Hello $name โ literalDouble quotes allow expansion while preventing word splitting. This is your default.
Single quotes are entirely literal. Use them for regular expressions, awk and sed programs, tokens containing $, and embedded JSON. Getting this backwards inside an awk command is a classic reason a pipeline silently produces nothing.
Conditionals
if [[ -f "$file" ]]; then
echo "exists"
elif [[ -d "$file" ]]; then
echo "is a directory"
else
echo "missing"
fi| Test | True when |
|---|---|
-f "$p" | Exists and is a regular file |
-d "$p" | Exists and is a directory |
-e "$p" | Exists (anything) |
-s "$p" | Exists and is non-empty |
-r / -w / -x | Readable / writable / executable |
-z "$s" | String is empty |
-n "$s" | String is non-empty |
"$a" == "$b" | Strings equal |
"$a" =~ ^re$ | Matches a regex |
$a -eq $b | Numbers equal (-ne -lt -le -gt -ge) |
Use [[ ]], not [ ]. The double-bracket form is a Bash builtin that handles empty variables safely, supports =~ for regex, and allows && and || without escaping.
if [[ "$env" == "prod" && -f "$config" ]]; thenStrings use ==; numbers use -eq. Mixing them produces confusing results rather than errors.
Loops
for file in *.txt; do
echo "$file"
done
for i in {1..10}; do ... done
for i in $(seq 1 10); do ... done
while read -r line; do
echo "$line"
done < input.txt
while [[ $count -lt 10 ]]; do
((count++))
done
for item in "${array[@]}"; do ... donewhile read -r is the correct way to process a file line by line. The -r prevents backslash interpretation, and quoting "$line" preserves whitespace.
Do not loop over ls. for f in $(ls) breaks on any filename containing a space. Use a glob โ for f in *.txt โ which handles them correctly.
Arrays
items=("a" "b" "c")
items+=("d")
echo "${items[0]}" # first element
echo "${items[@]}" # all elements
echo "${#items[@]}" # count
declare -A config # associative array (Bash 4+)
config[host]="localhost"
config[port]=5432
echo "${config[host]}"Always use "${items[@]}" with quotes and @. The * form joins everything into a single string, and without quotes, elements containing spaces split apart.
Functions
greet() {
local name="$1"
local greeting="${2:-Hello}"
echo "$greeting, $name"
}
greet "Ada"
result=$(greet "Ada" "Hi")Always declare variables local. Without it, every variable in a function is global, and a variable named i inside a function will silently overwrite the caller's loop counter.
Functions return an exit status, not a value. Use echo plus command substitution to return data, and return 0 / return 1 to signal success or failure.
Exit Codes and Error Handling
command || echo "failed"
command && echo "succeeded"
if ! command; then
echo "failed" >&2
exit 1
fi
echo "error message" >&2 # write errors to stderr
exit 1 # non-zero = failure$? holds the exit status of the last command, but with set -e the script usually exits before you can read it. To handle a failure deliberately, use || or an if:
if ! curl -sf "$url" -o out.json; then
echo "download failed" >&2
exit 1
fiWrite errors to stderr with >&2. Callers redirect stdout for data and stderr for diagnostics; mixing them makes your script unusable in a pipeline.
Cleanup With trap
tmpdir=$(mktemp -d)
cleanup() {
rm -rf "$tmpdir"
}
trap cleanup EXITtrap cleanup EXIT runs the function whenever the script terminates for any reason โ normal exit, an error under set -e, or Ctrl-C.
Without it, a script that fails halfway leaves its temporary directory behind on every run. In automation those accumulate until a disk fills.
mktemp -d creates a uniquely named directory, so concurrent runs do not collide. Never hardcode /tmp/mydir.
Arguments
"$1" "$2" # positional arguments
"$@" # all arguments, correctly quoted
"$#" # count
"$0" # script name
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <file>" >&2
exit 1
fi
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose) verbose=true; shift ;;
-o|--output) output="$2"; shift 2 ;;
*) echo "Unknown: $1" >&2; exit 1 ;;
esac
done"$@" not "$*". The former preserves each argument as a separate word; the latter joins them into one string, which breaks any argument containing a space.
Command Substitution and Arithmetic
today=$(date +%Y-%m-%d)
count=$(wc -l < file.txt)
((sum = a + b))
((count++))
result=$((a * b))Use $( ), not backticks. It nests correctly and is far easier to read.
Useful Patterns
# script's own directory, regardless of where it is called from
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# require a command to be installed
command -v jq >/dev/null || { echo "jq required" >&2; exit 1; }
# only run if a file changed
[[ src/app.js -nt dist/app.js ]] && npm run build
# retry with backoff
for i in {1..3}; do
curl -sf "$url" && break
sleep $((i * 2))
doneSCRIPT_DIR is the one to keep. Without it, a script that reads a sibling file works when run from its own directory and breaks from anywhere else.
The Five Mistakes
1. Missing set -euo pipefail. The script continues after failures and reports success.
2. Unquoted variables. Word splitting turns one path into several; an empty variable turns rm -rf "$D"/ into rm -rf /.
3. for f in $(ls). Breaks on spaces. Use a glob.
4. Variables not declared local in functions. Silently overwrites the caller's state.
5. Single quotes where double were needed. No expansion happens and the script behaves as though the variable is empty.
Print This Section
HEADER #!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
QUOTING "$var" always ยท '$var' is literal ยท "${arr[@]}" for arrays
TEST [[ -f "$f" ]] [[ -d "$d" ]] [[ -z "$s" ]] [[ "$a" == "$b" ]]
strings == ยท numbers -eq
SAFETY trap cleanup EXIT ยท tmp=$(mktemp -d)
local inside every function
errors โ >&2 ยท exit 1 on failure
NEVER for f in $(ls) โ for f in *.txt
rm -rf $DIR โ rm -rf "$DIR"If you add one thing to every script you write from now on, make it set -euo pipefail. It converts silent, partial, dangerous failure into a script that stops at the exact line something went wrong.
๐ Next in this collection: Regex Cheat Sheet With Copy-Paste Patterns, or return to The Complete Developer Cheat Sheet Collection.
Advertisement
๐ฌ DiscussionPowered by GitHub Discussions
Frequently Asked Questions

AI & Software Engineering Editorial Team
The AiTechWorlds editorial team writes and reviews in-depth guides on artificial intelligence, machine learning, prompt engineering, programming, and developer tools. Every article is fact-checked against primary sources and kept up to date for working developers and CS students.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of โBash Scripting Cheat Sheet (The First Line Every Script Needs)โ.
Advertisement
Related Articles
15 Coding Interview Patterns That Solve Most Problems
The 15 coding interview patterns that cover most problems โ the signal that identifies each one, a minimal code skeleton, and a two-minute recognition table.
CSS and Tailwind Cheat Sheet: Flexbox, Grid and Centering Solved
A practical CSS and Tailwind cheat sheet โ Flexbox vs Grid decision rules, every centering method, responsive breakpoints, and the specificity traps.
Data Structures and Big-O Cheat Sheet for Coding Interviews
A practical big O cheat sheet โ the complexity ladder with real examples, data structure operation tables, and how to state your complexity in an interview.
The Complete Developer Cheat Sheet Collection: 20 References Worth Saving
Twenty programming cheat sheets every developer should save โ Python, Git, SQL, Docker, system design and more, with what to memorise and what to look up.