Skip to content
5 min read·Lesson 3 of 10

Variables and Quoting

How variables work in Bash, and the quoting rules that prevent the most common scripting bugs.

Variables and quoting look trivial in any other language. In Bash they are the source of more bugs than any other feature. The rules are short and worth memorising.

Assignment

NAME=alice          # OK
COUNT=42            # OK
NAME = alice        # BROKEN — Bash thinks "NAME" is a command
GREETING="hello"    # quotes optional for simple values
PATH_LIST="/usr/bin:/usr/local/bin"

No spaces around =. The shell would otherwise treat the name as a command and fail.

Use

echo $NAME
echo "$NAME"           # better — handles spaces and globs
echo "${NAME}"          # explicit braces — required when joined
echo "${NAME}_backup"   # without braces, shell looks for variable NAME_backup

Single vs Double Quotes

NAME=alice

echo "Hello, $NAME"     # Hello, alice
echo 'Hello, $NAME'     # Hello, $NAME   (literal)

echo "Today: $(date)"   # command substitution works
echo 'Today: $(date)'   # literal
  • Single quotes — everything is literal. No expansion. No escapes.
  • Double quotes — variables and command substitutions expand; backslash escapes special chars.
  • No quotes — also expands, but also splits on whitespace and globs filenames. Usually wrong.

Why Quote, Always

FILE="my report.txt"

rm $FILE        # tries to delete two files: "my" and "report.txt"
rm "$FILE"      # correct — one file with a space in its name

The single most important rule in Bash: quote your variable expansions. Always. Even when you think the value is simple. ShellCheck (covered later) catches missing quotes for you.

Command Substitution

NOW=$(date +%Y-%m-%d)
COUNT=$(grep -c error /var/log/app.log)
USER=$(whoami)

LIST="$(ls *.txt)"
echo "Today is $NOW"

Use $(...), not the legacy backticks `...`. The new form nests cleanly and is easier to read.

Arithmetic

N=$((3 + 4))
((N++))
((N += 2))
echo "$N"

if (( N > 5 )); then
  echo "big"
fi

Bash arithmetic is integer-only. For floats use bc or awk.

Default Values and Substitutions

echo "${NAME:-anonymous}"     # use anonymous if NAME unset/empty
echo "${NAME:=anonymous}"     # also assign default
echo "${NAME:?must be set}"   # error and exit if unset
echo "${NAME:+is set}"        # alt value if NAME is set
echo "${PATH#*:}"             # strip shortest prefix matching pattern
echo "${FILE%.txt}"           # strip suffix
echo "${FILE//foo/bar}"       # replace all foo with bar

These parameter expansions replace many uses of sed and external tools. Worth memorising the most common five: :-, :?, #, %, //.

Arrays

FILES=(a.txt b.txt "c with space.txt")

echo "${FILES[0]}"            # first element
echo "${FILES[@]}"            # all elements
echo "${#FILES[@]}"           # count

for f in "${FILES[@]}"; do    # always quote
  echo "$f"
done

Use arrays whenever you have a list of values. Storing a list as a space-separated string and later splitting it is a common source of bugs.

Environment vs Shell Variables

FOO=bar           # shell variable — only in this shell
export FOO        # promote to environment — child processes see it

# or in one line:
export FOO=bar

env               # show environment
echo "$FOO"
unset FOO

Configuration that subprocesses need (DATABASE_URL, API keys) must be exported. Pure scripting state need not be.

Special Variables

VariableMeaning
$0script name
$1..$9positional arguments
$#argument count
$@all args (use as "$@" to keep them separate)
$*all args as one string
$?exit code of last command
$$PID of the shell
$!PID of last background process

Heredocs

cat <<EOF
Hello, $USER
Today is $(date)
EOF

cat <<'EOF'    # quoted EOF — no expansion
This is literal: $USER
EOF

Useful for embedding multi-line strings — config files, SQL, here-document arguments to other commands.

Read

read -p "Name: " NAME
read -s -p "Password: " PASS    # silent
echo
echo "Hello, $NAME"

Common Quoting Bugs

# Bug: spaces in path
DIR=/tmp/my data
ls $DIR              # tries to list /tmp/my and "data"

# Fix:
DIR="/tmp/my data"
ls "$DIR"

# Bug: empty variable becomes nothing
rm -rf $TARGET/      # if TARGET="", this is rm -rf /

# Fix:
[ -n "$TARGET" ] || { echo "TARGET unset"; exit 1; }
rm -rf "$TARGET"/

Cert Mapping

CertScope
RHCSA / LFCSVariables, environment, sourcing config files
AWS SAAUser data scripts, parameter store environment

The next lesson covers control flow — the if, while, and for that turn a list of commands into a program.

Key Takeaways

  • No spaces around = when assigning: NAME=value, not NAME = value.
  • Always quote variable expansions: "$var", not $var.
  • Single quotes are literal; double quotes expand variables and command substitutions.
  • Command substitution uses $(...) — not the legacy backticks.
  • Environment variables flow to child processes via export.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →