LFCS 121 ๐ง Shell Scripting โ Variables and Quoting
The previous chapter introduced the first script: the shebang, the executable permission, and the exit codes. This chapter focuses on the most error-prone part of shell scripting: variables and quoting. A variable holds a string, but the shell treats unquoted strings as a set of words to be split and expanded. A quoted string is a single word, with the expansions controlled by the quote type. The LFCS exam tests the difference between single quotes, double quotes, and no quotes, the use of ${} to delimit variable names, and the command substitution syntax. The critical trap is the unquoted variable that contains a space: the shell splits it into multiple arguments, and the command receives the wrong number of arguments.
Key point: Unquoted variables are subject to word splitting and globbing. Double quotes ("$var") prevent word splitting and globbing but allow variable expansion, command substitution, and arithmetic expansion. Single quotes ('$var') prevent all expansionโthe literal string is passed. The ${var} syntax is required when the variable name is followed by a character that could be part of the name, or when a default value is specified (${var:-default}).
Why quoting and variable expansion require precise rules
The word-splitting problem. The shell performs word splitting on unquoted expansions. A variable containing hello world (with a space) is split into two words when expanded unquoted. A command that expects one argument receives two. A loop that iterates over a list of filenames with spaces in their names fails because each filename becomes multiple iterations. The fix is to quote the variable: "$var" is a single word, with the space preserved .
The globbing problem. An unquoted variable that contains * or ? is subject to pathname expansion. The files="*.txt" assignment followed by ls $files lists every .txt file in the directory, not the literal string *.txt. The "$files" form passes the literal string. The behavior is the same for [abc] character classes and brace expansions.
The single-versus-double quote problem. Double quotes allow variable expansion, command substitution, and arithmetic expansion. Single quotes prevent all of them. The "$var" form expands the variable. The '$var' form passes the literal string $var. The difference matters when the value contains characters that the shell would otherwise interpret: $, \, backtick, " .
The variable name delimiter problem. The $var_suffix form is ambiguous: the shell interprets the variable name as var_suffix, not var. The ${var}_suffix form makes the variable name explicit. The braces are also required for parameter expansion operators like ${var:-default} (use default if unset), ${var:=default} (assign default if unset), and ${var:?message} (error if unset) .
The command substitution problem. The $(command) syntax runs the command and substitutes its output. The old backtick syntax (`command`) is deprecated but still works. The $(...) form is preferred because it nests without escaping. The output is subject to word splitting unless quoted. The "$(command)" form is the correct pattern for capturing the output as a single string.
The trade-off. Quoting everything is verbose. Not quoting is dangerous. The practical rule is: quote every variable expansion unless you specifically want word splitting or globbing. The exam tests the distinction because the difference between $var and "$var" is the difference between a working script and a script that breaks on filenames with spaces.
a. Variable assignment and expansion
A variable is assigned without spaces around the =. The value is a string. The reference is $name or ${name}.
# Assignment
name="World"
count=42
# Reference
echo "Hello, $name"
echo "Count: ${count}"
# Delimit the name with braces
echo "${name}_suffix"
# Output: World_suffix
# Without braces, the shell looks for "name_suffix"
echo "$name_suffix"
# Output: (empty)
The name_suffix reference is empty because the variable name_suffix is not set. The ${name}_suffix form is the correct way to append a literal suffix to a variable’s value.
A variable can be unset with unset. The unset name command removes the variable. The ${name:-default} form returns the default value if the variable is unset or empty, without modifying the variable.
# Unset the variable
unset name
# Use a default value
echo "Hello, ${name:-stranger}"
# Output: Hello, stranger
# The variable is still unset
echo "Name: ${name:-none}"
# Output: Name: none
The ${name:=default} form assigns the default value to the variable if it is unset or empty. The ${name:?message} form prints the message and exits the script if the variable is unset or empty. These are the correct patterns for required variables.
b. Single quotes, double quotes, and no quotes
The three quoting forms have different effects on the string.
name="World"
# No quotes: word splitting and globbing apply
echo $name
# Output: World
# Double quotes: no splitting, but expansion
echo "$name"
# Output: World
# Single quotes: no expansion
echo '$name'
# Output: $name
The no-quotes form is dangerous when the value contains spaces or glob characters. The "$name" form is the safe default. The '$name' form is for literal strings that should not be expanded.
The difference is visible when the value contains a space.
greeting="hello world"
# No quotes: two arguments
echo $greeting
# Output: hello world
# Double quotes: one argument
echo "$greeting"
# Output: hello world
# The difference matters for commands that check argument count
printf '%s\n' $greeting
# Output: hello
# world
printf '%s\n' "$greeting"
# Output: hello world
The printf command with %s\n prints each argument on a separate line. The unquoted variable produces two lines. The quoted variable produces one. The difference is the argument count, not the output.
c. Command substitution and arithmetic expansion
Command substitution captures the output of a command. The $(command) form is the modern syntax.
# Capture the current date
today=$(date +%Y-%m-%d)
echo "Today is $today"
# Capture the output of a command that fails
files=$(ls /nonexistent 2>/dev/null)
echo "Files: $files"
# The exit code is the exit code of the last command in the assignment.
The exit code of the assignment is the exit code of the command substitution. The $? variable after the assignment returns the command’s exit code, not the assignment’s. This is the mechanism for checking whether a command substitution succeeded.
The "$(command)" form preserves the output as a single string, including newlines. The unquoted $(command) form splits the output on whitespace, which is useful for iterating over lines.
# Quote to preserve newlines
output="$(ls -1)"
echo "$output"
# Unquote to split into words
for file in $(ls -1); do
echo "File: $file"
done
Arithmetic expansion uses the $((expression)) syntax. The expression is evaluated using integer arithmetic. The variables inside the expression do not require the $ prefix.
a=5
b=3
sum=$((a + b))
echo "Sum: $sum"
# Output: Sum: 8
# Increment
((a++))
echo "a: $a"
# Output: a: 6
The ((...)) form is a command that evaluates the expression and returns success if the result is non-zero, failure if the result is zero. This is used for arithmetic conditions in if statements.
Complete Example Session
# ============================================
# PART 1: BASIC VARIABLE
# ============================================
name="World"
echo "Hello, $name"
# Output: Hello, World
# ============================================
# PART 2: BRACES FOR DELIMITING
# ============================================
echo "${name}_suffix"
# Output: World_suffix
echo "$name_suffix"
# Output: (empty)
# ============================================
# PART 3: DEFAULT VALUE
# ============================================
unset name
echo "Hello, ${name:-stranger}"
# Output: Hello, stranger
# ============================================
# PART 4: SINGLE VS DOUBLE QUOTES
# ============================================
name="World"
echo "Hello, $name"
# Output: Hello, World
echo 'Hello, $name'
# Output: Hello, $name
# ============================================
# PART 5: WORD SPLITTING
# ============================================
greeting="hello world"
printf '%s\n' $greeting
# Output: hello
# world
printf '%s\n' "$greeting"
# Output: hello world
# ============================================
# PART 6: GLOBBING
# ============================================
files="*.txt"
echo $files
# Output: a.txt b.txt c.txt
echo "$files"
# Output: *.txt
# ============================================
# PART 7: COMMAND SUBSTITUTION
# ============================================
today=$(date +%Y-%m-%d)
echo "Today: $today"
# Output: Today: 2026-10-29
# ============================================
# PART 8: ARITHMETIC
# ============================================
a=5
b=3
echo $((a + b))
# Output: 8
# ============================================
# PART 9: REQUIRED VARIABLE
# ============================================
# In a script:
# : "${REQUIRED_VAR:?REQUIRED_VAR is not set}"
# If REQUIRED_VAR is unset, the script exits with the message.
The nine parts trace the quoting rules: basic variable, braces, default value, quote types, word splitting, globbing, command substitution, arithmetic, and required variable.
Quick Reference
Variable Syntax
| Syntax | Purpose |
|---|---|
var=value | Assign (no spaces) |
$var | Reference |
${var} | Reference with braces |
${var:-default} | Default if unset |
${var:=default} | Assign default if unset |
${var:?message} | Error if unset |
Quote Types
| Quote | Expansion | Word Splitting |
|---|---|---|
| None | Yes | Yes |
"..." | Yes | No |
'...' | No | No |
Expansion Types
| Syntax | Purpose |
|---|---|
$var | Variable |
$(command) | Command substitution |
$((expr)) | Arithmetic |
$((a++)) | Increment |
Special Variables
| Variable | Value |
|---|---|
$0 | Script name |
$1, $2 | Positional parameters |
$# | Number of parameters |
$@ | All parameters (separate) |
$* | All parameters (joined) |
$? | Exit code of last command |
Best Practices
โ Do This:
echo "$var"
<!-- Quote every variable expansion --> # โ
echo "${var}_suffix"
<!-- Use braces to delimit the name --> # โ
: "${REQUIRED:?REQUIRED is not set}"
<!-- Fail early if required variable is unset --> # โ
โ Don’t Do This:
echo $var
<!-- Word splitting and globbing apply --> # โ
var = value
<!-- Spaces around = are parsed as a command --> # โ
echo '$var'
<!-- Single quotes prevent expansion --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Filename with space fails | Unquoted variable | "$var" |
*.txt expanded unexpectedly | Globbing | "$var" |
| Variable name ambiguous | No braces | ${var}_suffix |
| Script continues with empty variable | No default/required check | ${var:?message} |
| Assignment is a command | Spaces around = | var=value |
Real-World Examples
1. Assignment
name="World"
2. Reference
echo "$name"
3. Braces
echo "${name}_suffix"
4. Default
echo "${name:-stranger}"
5. Required
: "${name:?name is required}"
6. Single Quote
echo '$name'
7. Command Substitution
today=$(date +%Y-%m-%d)
8. Arithmetic
sum=$((5 + 3))
9. Increment
((count++))
10. Quote Command Output
output="$(ls)"
Visual
Quote Types
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ "$var" (double quotes) โ
โ โโโ Variable expansion: YES โ
โ โโโ Command substitution: YES โ
โ โโโ Word splitting: NO โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ '$var' (single quotes) โ
โ โโโ Variable expansion: NO โ
โ โโโ Command substitution: NO โ
โ โโโ Literal string โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ $var (no quotes) โ
โ โโโ Variable expansion: YES โ
โ โโโ Word splitting: YES โ
โ โโโ Globbing: YES โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Word Splitting
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ greeting="hello world" โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ echo $greeting โ
โ โ hello world (one line, two args) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ echo "$greeting" โ
โ โ hello world (one line, one arg) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Assignment | var=value (no spaces) |
| Reference | $var or ${var} |
| Double quotes | Expansion, no splitting |
| Single quotes | No expansion |
| No quotes | Expansion, splitting, globbing |
| Default value | ${var:-default} |
| Required value | ${var:?message} |
| Command substitution | $(command) |
| Arithmetic | $((expr)) |
Key takeaways:
- Quote every variable expansion. The unquoted
$varis subject to word splitting and globbing. A filename with a space becomes multiple arguments. A*in the value is expanded to matching files. The"$var"form preserves the value as a single string . - Braces delimit the variable name. The
${var}_suffixform is unambiguous. The$var_suffixform looks for a variable namedvar_suffix. - Single quotes prevent all expansion. The
'$var'form passes the literal string$var. Use it when the$is not a variable reference. - Default and required values are parameter expansion operators. The
${var:-default}form returns the default if the variable is unset. The${var:?message}form exits with the message if the variable is unset. - Command substitution captures output. The
$(command)form runs the command and substitutes its output. The"$(command)"form captures the output as a single string, including newlines.
Remember: The shell is a string-processing engine. The variable is a string. The quotes control how the string is expanded and split. The single quotes are literal. The double quotes allow expansion. The no-quotes form is the dangerous one. The braces are the delimiter. The exam tests the difference between the three forms and the parameter expansion operators.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!