Variables, Quoting & Substitution
Variables, Quoting & Substitution
Quoting bugs are the single most common source of broken shell scripts at every level of experience — from intern to principal. A senior SRE at a large tech company will still grep their own scripts for unquoted variables before a production deploy. This lesson teaches you why quoting matters at a fundamental level, how the shell parses your input, and the exact patterns used in production-grade automation.
Variable Assignment and Scope
Assign a variable with NAME=value — no spaces around the =. The shell is sensitive to whitespace here; NAME = value is a command invocation, not assignment. Reference the value with $NAME or the safer ${NAME}. The braces form is required whenever the variable name is followed immediately by alphanumeric characters that would otherwise be parsed as part of the name.
export the variable first. Use export VARNAME or declare and export in one step: export VARNAME="value". Environment variables passed to Docker, CI systems, and process managers all follow this rule.
The Three Quoting Modes
The shell has three quoting modes, each with a different effect on how the content is interpreted. Misunderstanding this is where most production bugs originate.
The production rule is simple: always double-quote variable references unless you have a specific reason not to. Files on real servers frequently contain spaces, tabs, and newlines in their names. An unquoted $filename in a rm or cp command will word-split and match multiple targets — a disaster in production.
Command Substitution
Command substitution captures the standard output of a command and injects it into an expression. The modern form is $(command). The legacy form uses backticks — avoid backticks in new code because they cannot be nested and are harder to read.
"$(command)", not $(command). The output of commands can contain spaces and newlines. A bare $(find . -name "*.log") would word-split every filename with a space in it. Quoting prevents that split.
Arithmetic Expansion
The shell natively supports integer arithmetic via $(( expression )). This is faster than invoking expr (a common legacy pattern) and handles standard operator precedence correctly. For floating-point math, delegate to bc or python3.
Special Variables the Shell Sets for You
Several variables are pre-populated by the shell itself. Knowing them removes the need to pass redundant arguments into scripts and is expected knowledge at the SRE level.
$0— the name of the script itself, useful in usage messages$?— the exit code of the last command (0 = success)$$— the PID of the current shell, useful for creating unique temp file names$!— the PID of the last background process$IFS— the Internal Field Separator used for word splitting (default: space, tab, newline)
IFS to parse CSV data or split on colons, always restore it afterward. A common pattern is to save the original: OLD_IFS="$IFS", change it, then restore with IFS="$OLD_IFS". Forgetting to restore IFS causes every subsequent word-splitting operation in the script to silently behave wrong — a very hard bug to trace.
Variable Defaults and Defensive Patterns
Production scripts must tolerate missing or empty variables without causing data loss. Bash provides a set of parameter expansion operators for this. At top-tier companies these patterns appear in every automation script because unset variables in a rm -rf command have deleted production data.
The combination of ${VAR:?message} at the top of a destructive script and set -euo pipefail (covered in Lesson 8) forms the safety net that distinguishes production-grade scripts from ad-hoc one-liners. Internalize both before you write any automation that touches data.