| | |

LFCA 57 ๐Ÿง Shell Scripting โ€” Variables and Quoting

In the previous chapter, you wrote a Hello World script, made it executable, and passed arguments to it. Those commands were static โ€” the same words, the same paths, every time the script ran. Real scripts are not static. They accept filenames that change, dates that vary, and user input that arrives at runtime. Variables are how shell scripts hold that data. Quoting is how they control what the shell does with it.

The relationship between variables and quoting is the single most important concept to understand about Bash. A variable without quotes is a variable that the shell will split at every space and expand into every matching filename. That behavior is occasionally useful and frequently catastrophic. A variable with the wrong kind of quotes is a variable that refuses to expand at all, or one that expands into something you did not intend. The LFCA exam tests this under the “Command Line” competency, and the failure modes are exactly what the exam’s scenario questions are designed to catch .

This chapter covers variable creation, reference syntax, the three quoting mechanisms, the exact order in which the shell processes a command, and the rules that determine when a variable expands and when it does not.

Key point: The shell processes a command in five stages: parsing, parameter and command substitution, word splitting, filename generation, and quote removal . Quoting is not a single operation โ€” it is the set of rules that determines which of those stages apply to a given piece of text. Single quotes suppress all of them except quote removal. Double quotes suppress some but allow parameter and command substitution. Backslashes suppress exactly one character. The exam tests the differences, not the definitions.


Why quoting exists

The shell is not a programming language in the conventional sense. It is a text processor that decides what to do based on characters. Spaces separate arguments. Asterisks match filenames. Dollar signs introduce variables. Semicolons end commands. Every one of these characters has a special meaning by default .

Quoting is the act of taking that special meaning away. When you quote a character, you tell the shell: treat this literally, do not interpret it . The choice of quoting mechanism determines how much you take away and how long you take it away for.

The reason this matters is that variables hold text, and that text often contains characters the shell would otherwise interpret. A variable holding a filename with a space in it โ€” /home/user/My Documents โ€” is the canonical example. Unquoted, the shell sees two words: /home/user/My and Documents. The command receives two arguments instead of one. Quoted, the shell sees one word, and the command receives the filename correctly .

The same problem applies to asterisks. A variable holding *.txt unquoted expands to every text file in the current directory. Quoted, it stays *.txt . The variable does not know what it holds. Quoting is how you tell the shell what to do with it.


a. Variable Assignment and Reference

Variables in Bash are created with the name=value syntax. There are no spaces around the equals sign. NAME="Alice" is correct. NAME = "Alice" is three separate words: a command named NAME, an argument =, and an argument Alice โ€” which fails with “command not found” .

#!/bin/bash
NAME="Alice"
AGE=30
echo "$NAME is $AGE years old"

The value on the right side of the equals sign undergoes tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal โ€” but not word splitting and not filename expansion . This is a critical distinction. The value is not re-split into words after substitution. MESSAGE="Hello $NAME" stores the string Hello Alice as a single value, even though it contains a space.

To reference a variable, prefix its name with $. $NAME expands to the value. ${NAME} does the same thing but is unambiguous when the variable is adjacent to other text. In echo "${NAME}world", the braces make it clear that the variable is NAME, not NAMEworld . The braces are optional when followed by whitespace or punctuation, but they are always safe.

NAME="Alice"
echo "$NAME"         # Alice
echo "${NAME}world"  # Aliceworld
echo "$NAMEworld"    # empty โ€” variable NAMEworld does not exist

There are three categories of variables in Bash: shell variables, which exist only in the current shell; environment variables, which are inherited by child processes; and special parameters, which are set by the shell itself and cannot be assigned . The export command converts a shell variable into an environment variable . Without export, a variable set in a script is invisible to any command or subprocess the script launches.

NAME="Alice"          # shell variable only
export NAME           # now inherited by child processes
export NAME="Alice"   # set and export in one statement

The unset command removes a variable entirely . readonly NAME prevents reassignment . declare -n ref=$1 creates a nameref โ€” a reference to another variable whose name is passed as an argument, commonly used in functions .


b. Single Quotes: Total Suppression

Single quotes are the most absolute form of quoting. Everything between the opening ' and the closing ' is taken literally. Not a single character retains its special meaning .

echo 'Hello $NAME'      # Hello $NAME โ€” no expansion
echo 'The price is $5'  # The price is $5 โ€” literal dollar sign
echo '*.txt'            # *.txt โ€” no filename expansion
echo 'It'\''s here'     # It's here โ€” see below for the escape

Inside single quotes, $, \, `, *, ?, and every other metacharacter is just a character. The shell performs only quote removal โ€” it deletes the single quotes and passes the literal content to the command . This is the safest quoting for text that should never be interpreted, such as strings containing dollar signs, backslashes, or wildcard characters meant literally.

The one thing you cannot do inside single quotes is include a single quote. The shell has no escape mechanism inside single quotes โ€” a backslash is just a backslash. To embed a single quote, you must close the quotes, add an escaped quote, and reopen: 'It'\''s' . This is read as 'It' + \' + 's', producing It's.

Single quotes are the right choice for literal strings, regular expressions passed to tools like grep, and any text where expansion is not wanted .


c. Double Quotes: Selective Suppression

Double quotes are less absolute. They suppress the special meaning of most characters, but they allow parameter expansion ($), command substitution (` or $(...)), arithmetic expansion ($((...))), and backslash escaping of specific characters .

NAME="Alice"
echo "Hello $NAME"           # Hello Alice โ€” expansion happens
echo "The price is \$5"      # The price is $5 โ€” backslash escapes $
echo "Today is $(date)"      # Today is <date output> โ€” command runs
echo "Path: /home/$USER"     # Path: /home/<username>

The four characters that retain special meaning inside double quotes are $, `, \, and " itself . The backslash inside double quotes only escapes those four characters (and a newline). A backslash before any other character is taken literally โ€” it remains in the output .

Double quotes are the default quoting for variable references. The rule of thumb from decades of shell practice: “variable references should almost always be contained within double quotes; failure to do so is the main reason why scripts fail on filenames which contain spaces” . If you quote nothing else in a script, quote every $VAR and $(command) in double quotes.

The combination of double quotes and $@ is a special case worth knowing. "$@" expands each positional parameter as a separate word, preserving the boundaries between them even if individual parameters contain spaces . Unquoted $@ splits each parameter at its internal spaces. This is the correct way to forward arguments to another command.


Complete Example Session

This session builds a script that demonstrates each quoting mechanism, the difference between quoted and unquoted expansion, and the failure cases that quoting prevents.

#!/bin/bash
# quoting-demo.sh - Demonstrates Bash quoting behavior

# ============================================
# PART 1: THE UNQUOTED VARIABLE TRAP
# ============================================

FILENAME="My Document.txt"
touch "$FILENAME"                    # creates a file with a space in the name
ls $FILENAME                         # โŒ tries to list two files: "My" and "Document.txt"
ls "$FILENAME"                       # โœ… lists the one file

# ============================================
# PART 2: THE GLOB EXPANSION TRAP
# ============================================

PATTERN="*.txt"
echo $PATTERN                        # โŒ expands to every .txt file in the directory
echo "$PATTERN"                      # โœ… prints *.txt literally
echo '$PATTERN'                      # โœ… prints $PATTERN literally

# ============================================
# PART 3: THE SINGLE QUOTE SUPPRESSION
# ============================================

NAME="Alice"
echo 'Hello $NAME'                   # prints: Hello $NAME
echo "Hello $NAME"                   # prints: Hello Alice

# ============================================
# PART 4: THE DOUBLE QUOTE EXPANSION
# ============================================

echo "Today is $(date +%Y-%m-%d)"    # command substitution works
echo 'Today is $(date +%Y-%m-%d)'    # prints the command literally

# ============================================
# PART 5: THE BACKSLASH ESCAPE
# ============================================

echo "The price is \$5"              # prints: The price is $5
echo "The price is $5"               # prints: The price is 5 (empty var $5)

# ============================================
# PART 6: THE BRACE DISAMBIGUATION
# ============================================

FILE="report"
echo "${FILE}port"                   # prints: reportport
echo "$FILEport"                     # prints: (empty) โ€” no variable FILEport

# ============================================
# PART 7: THE EXPORTED VARIABLE
# ============================================

export GREETING="Hello from parent"
bash -c 'echo $GREETING'             # prints: Hello from parent
UNEXPORTED="not visible"
bash -c 'echo $UNEXPORTED'           # prints: (empty)

# ============================================
# PART 8: THE POSITIONAL PARAMETER PRESERVATION
# ============================================

# Usage: ./script.sh "file one.txt" "file two.txt"
for arg in "$@"; do                  # โœ… preserves spaces in each argument
    echo "Argument: $arg"
done

for arg in $@; do                    # โŒ splits "file one.txt" into two arguments
    echo "Argument: $arg"
done

# ============================================
# PART 9: THE SAFE VARIABLE ASSIGNMENT
# ============================================

CITIES="London ; Paris ; New York"
echo "$CITIES"                       # โœ… one string: London ; Paris ; New York
echo $CITIES                         # โŒ multiple arguments to echo

# ============================================
# PART 10: THE READ WITH QUOTES
# ============================================

while IFS= read -r line; do          # โœ… preserves leading/trailing spaces
    echo "Line: $line"
done < input.txt

The ten parts cover the unquoted variable trap, glob expansion, single quote suppression, double quote expansion, backslash escaping, brace disambiguation, variable export, positional parameter preservation, safe assignment, and safe read.


Quick Reference

The Quoting Mechanisms

MechanismSuppressesAllowsUse For
UnquotedNothingEverythingRarely โ€” only when splitting is wanted
Single '...'EverythingNothing (literal)Literal strings, regex, dollar signs
Double "..."Most things$, `, \, "Variable references, command substitution
Backslash \xNext character onlyโ€”Escaping one metacharacter

The Expansion Order

StageWhat HappensAffected By
1. ParsingSplit into words, detect quotes', ", \, spaces
2. Parameter/command substitution$VAR, $(cmd) expandDouble quotes allow, single suppress
3. Word splittingSplit on IFS charactersQuoting suppresses
4. Filename generation*, ? expandQuoting suppresses
5. Quote removalQuotes deletedโ€”

The Special Characters

CharacterUnquotedSingle QuotedDouble Quoted
$expansionliteralexpansion
*globliteralliteral
`command subliteralcommand sub
\escapeliteralescapes $ \ “`
spaceword splitliteralliteral

Best Practices

โœ… Do This:

FILENAME="My Document.txt"                               # โœ…
ls "$FILENAME"                                           # โœ… always quote variables
echo "${FILE}port"                                       # โœ… braces for clarity
export API_KEY                                           # โœ… export before use in subprocess
for arg in "$@"; do                                      # โœ… preserve argument boundaries
CITIES="London ; Paris ; New York"                       # โœ… single value with semicolons
while IFS= read -r line; do                              # โœ… safe file reading

โŒ Don’t Do This:

NAME = "Alice"                                           # โŒ spaces around =
ls $FILENAME                                             # โŒ word splitting on spaces
echo $PATTERN                                            # โŒ glob expansion
echo "$FILEport"                                         # โŒ ambiguous variable boundary
for arg in $@; do                                        # โŒ splits arguments with spaces
CITIES=London ; Paris ; New York                         # โŒ semicolon ends assignment

Common Pitfalls

PitfallWhy It HappensFix
“Command not found” on assignmentSpaces around =Use NAME="value"
Filename with space failsUnquoted variableQuote every $VAR
Glob expands unexpectedlyUnquoted variable holding *Quote or single-quote
Variable appears emptyWrong name or not exportedCheck spelling, use export
$FILEport emptyShell reads FILEport as nameUse ${FILE}port
$@ splits argumentsUnquoted expansionUse "$@"
Semicolon ends assignmentUnquoted valueQuote the whole value

Real-World Examples

1. Safe Filename Handling

FILENAME="My Document.txt"
ls "$FILENAME"

2. Literal Dollar Sign

echo 'The cost is $5.00'

3. Command Substitution with Double Quotes

echo "Today is $(date)"

4. Brace Disambiguation

FILE="report"
echo "${FILE}port"

5. Export for Subprocess

export DATABASE_URL="postgres://localhost/db"

6. Forward Arguments Safely

for arg in "$@"; do echo "$arg"; done

7. Regex Passed to grep

grep '^[0-9]\{3\}$' file.txt

8. Multi-Word Value

CITIES="London ; Paris ; New York"
echo "$CITIES"

9. Safe File Reading

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

10. Escape Inside Double Quotes

echo "The value is \"$VAR\""

Visual: Quoting Mechanisms

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  QUOTING MECHANISMS                          โ”‚
โ”‚                                              โ”‚
โ”‚  'Single'    โ†’  Everything literal           โ”‚
โ”‚                 $NAME stays $NAME             โ”‚
โ”‚                                              โ”‚
โ”‚  "Double"    โ†’  $, `, \, " still work        โ”‚
โ”‚                 $NAME becomes Alice          โ”‚
โ”‚                                              โ”‚
โ”‚  \Backslash  โ†’  Next character literal       โ”‚
โ”‚                 \$5 becomes $5               โ”‚
โ”‚                                              โ”‚
โ”‚  None        โ†’  Everything works             โ”‚
โ”‚                 $NAME splits and globs       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Expansion Order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  EXPANSION ORDER                             โ”‚
โ”‚                                              โ”‚
โ”‚  1. Parsing                                  โ”‚
โ”‚     โ””โ”€ Detect quotes, split words            โ”‚
โ”‚                                              โ”‚
โ”‚  2. Substitution                             โ”‚
โ”‚     โ””โ”€ $VAR โ†’ value, $(cmd) โ†’ output         โ”‚
โ”‚        (single quotes suppress this)         โ”‚
โ”‚                                              โ”‚
โ”‚  3. Word Splitting                           โ”‚
โ”‚     โ””โ”€ Split on spaces                       โ”‚
โ”‚        (quoting suppresses this)             โ”‚
โ”‚                                              โ”‚
โ”‚  4. Filename Generation                      โ”‚
โ”‚     โ””โ”€ *.txt โ†’ file1.txt file2.txt           โ”‚
โ”‚        (quoting suppresses this)             โ”‚
โ”‚                                              โ”‚
โ”‚  5. Quote Removal                            โ”‚
โ”‚     โ””โ”€ Delete the quote characters           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: The Unquoted Variable Trap

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  UNQUOTED VARIABLE TRAP                      โ”‚
โ”‚                                              โ”‚
โ”‚  FILENAME="My Document.txt"                  โ”‚
โ”‚                                              โ”‚
โ”‚  ls $FILENAME                                โ”‚
โ”‚    โ””โ”€ shell sees: ls My Document.txt         โ”‚
โ”‚    โ””โ”€ TWO arguments: "My" and "Document.txt" โ”‚
โ”‚    โ””โ”€ โŒ error or wrong output               โ”‚
โ”‚                                              โ”‚
โ”‚  ls "$FILENAME"                              โ”‚
โ”‚    โ””โ”€ shell sees: ls "My Document.txt"       โ”‚
โ”‚    โ””โ”€ ONE argument: "My Document.txt"        โ”‚
โ”‚    โ””โ”€ โœ… correct                             โ”‚
โ”‚                                              โ”‚
โ”‚  Rule: quote every $VAR unless you have      โ”‚
โ”‚  a specific reason not to.                   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Single vs Double Quotes

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SINGLE vs DOUBLE QUOTES                     โ”‚
โ”‚                                              โ”‚
โ”‚  NAME="Alice"                                โ”‚
โ”‚                                              โ”‚
โ”‚  echo 'Hello $NAME'                          โ”‚
โ”‚    โ””โ”€ prints: Hello $NAME                    โ”‚
โ”‚    โ””โ”€ literal, no expansion                  โ”‚
โ”‚                                              โ”‚
โ”‚  echo "Hello $NAME"                          โ”‚
โ”‚    โ””โ”€ prints: Hello Alice                    โ”‚
โ”‚    โ””โ”€ $ expands, quotes removed              โ”‚
โ”‚                                              โ”‚
โ”‚  echo 'Today is $(date)'                     โ”‚
โ”‚    โ””โ”€ prints: Today is $(date)               โ”‚
โ”‚                                              โ”‚
โ”‚  echo "Today is $(date)"                     โ”‚
โ”‚    โ””โ”€ prints: Today is 2026-05-03            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
AssignmentNAME="value" โ€” no spaces around =
Reference$NAME or ${NAME}
Single quotesSuppress everything, fully literal
Double quotesAllow $, `, \, "
BackslashEscape next character only
Exportexport VAR to pass to child processes
Word splittingSuppressed by quoting
Glob expansionSuppressed by quoting
Unquoted $@Splits arguments at spaces
Quoted "$@"Preserves argument boundaries

Key takeaways:

  • Variables are assigned without spaces. NAME="Alice" is correct. NAME = "Alice" is a command lookup that fails. The shell reads the value literally until the newline or semicolon, but spaces in the value require quotes .
  • Single quotes suppress everything. Inside '...', no character has special meaning. Dollar signs are literal, asterisks are literal, backslashes are literal. You cannot embed a single quote without closing and reopening .
  • Double quotes allow expansion but prevent splitting. Inside "...", $VAR expands, $(cmd) runs, and \ escapes. But the result stays as one word โ€” spaces do not split it, and asterisks do not glob .
  • Always quote variable references. The rule of thumb is that $VAR and $(cmd) should almost always be inside double quotes. Unquoted expansion is the primary cause of scripts failing on filenames with spaces .
  • Braces disambiguate variable names. ${FILE}port is unambiguous. $FILEport looks for a variable named FILEport .
  • Export passes variables to child processes. Without export, a variable set in a script is invisible to any command the script launches. export converts it into an environment variable .
  • "$@" preserves argument boundaries. Unquoted $@ splits each positional parameter at its internal spaces. Quoted "$@" forwards arguments exactly as they were received .

Remember: Variables hold text. Quoting controls what the shell does with that text. Single quotes say “take it literally.” Double quotes say “expand the variables, but keep it as one word.” No quotes say “split it, glob it, do whatever the shell would normally do.” The LFCA exam expects you to know which one to use and why. When in doubt, quote it โ€” and when you cannot quote it, know exactly why.


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!