Quoting and word splitting in bash: why “$var” and arrays save you
Single quotes, double quotes, no quotes and arrays: how bash splits words and expands globs, and the small set of habits that keep spaces in file names from breaking your scripts.
Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?
Most mysterious bash bugs come from one mechanism: after the shell expands $variables and $(commands), it splits the result into words on spaces, tabs and newlines, and then expands any * or ? against the file system. Quotes control which of those steps happen.
The three kinds of quoting
name="my report.txt"
echo '$name' # single quotes: literally $name, nothing expands
echo "$name" # double quotes: expands, result stays one word
echo $name # no quotes: expands, then splits into: my report.txt
touch "$name" # creates one file
touch $name # creates two files: my and report.txt
- Single quotes protect everything. Use them for fixed strings, regular expressions and
awkorsedprograms. - Double quotes expand
$var,$(cmd)and\escapes but keep the result as one word. This is the default you want around every variable. - No quotes means word splitting and glob expansion happen. It is only right when you want a list, for example
for f in *.log.
The special case of "$@"
Inside double quotes, "$@" expands to each argument as its own word, preserving spaces inside them. "$*" joins all arguments into one word. Unquoted $@ re-splits everything. Pass arguments on with "$@" almost always.
show() { printf '<%s>\n' "$@"; }
show "a b" c # <a b> <c>
show $(echo "a b" c) # <a> <b> <c>: command output was split
Arrays for lists
When you need a list of things, use a bash array instead of a space-separated string. Arrays keep elements with spaces intact and expand cleanly with "${arr[@]}".
files=("/tmp/a b.txt" "/tmp/c.txt")
files+=("/tmp/d.txt")
for f in "${files[@]}"; do
echo "processing: $f"
done
echo "${#files[@]} files" # count
echo "${files[0]}" # first element
# Build a command line safely
opts=(-v --color=auto)
[[ -n "${PATTERN:-}" ]] && opts+=(-e "$PATTERN")
grep "${opts[@]}" -- *.log
Collect command output into an array with mapfile (also called readarray), which splits on newlines only, or with a null-separated find for file names that may contain anything.
mapfile -t hosts < hosts.txt
mapfile -d '' logs < <(find /var/log -name '*.log' -print0)
echo "${#logs[@]} log files"
Globs and when to disable them
An unquoted * matches files in the current directory. That is useful in for f in *.jpg and dangerous in echo $pattern when pattern contains a star. If a glob matches nothing it stays as the literal text; set shopt -s nullglob in scripts so that an empty match becomes an empty list instead of a literal *.jpg.
shopt -s nullglob
for f in *.jpg; do
echo "$f"
done
# Stop option-looking file names from being read as options
rm -- "$f"
ls -l -- *
Checklist
- Quote every expansion:
"$var","$(cmd)","${arr[@]}","$@". - Use arrays for lists of files or options; never a string you later split.
- Use
--before file arguments that come from variables. - Use
[[ ]]rather than[ ]in bash: it does not word-split its operands. - Run
shellcheck script.sh; warnings SC2086 and SC2046 point exactly at missing quotes.
Official documentation
Did this work for you?
No votes yet. Be the first.
If it did not work, the report form below tells the editors what went wrong.
See also
- 0049 Pipes and redirection: connecting commands A pipe sends one command's output into another command, and redirection sends output to a file. Together they let small tools build… Beginner · Explains an idea · Linux, macOS Article Basics · Text processing 15 Oct 2026
- 0050 File permissions: chmod, chown and what 755 means Every file has an owner, a group and read/write/execute permissions for three kinds of users. chmod changes the permissions and chown changes… Beginner · Explains an idea · Linux, macOS Article Basics 15 Oct 2026
- 0051 Environment variables and PATH Environment variables are named settings that every program inherits from the shell. PATH is the one that tells the shell where to… Beginner · Explains an idea · Linux, macOS Article Basics 15 Oct 2026
- 0057 Shell cheat sheet: everyday commands A quick reference for moving around, managing files, viewing and searching text, checking processes and disk space in bash and zsh, with… Beginner · Cheat sheet · Linux, macOS Article Basics · Command-line tools 15 Oct 2026
- 0058 Safe bash script template with set ‑euo pipefail and trap A starting point for bash scripts that stops on errors, rejects unset variables, cleans up temp files on exit and parses options.… Intermediate · Cheat sheet · Linux, macOS Script Shell scripting 15 Oct 2026
- 0059 Incremental snapshot backups with rsync A bash script that copies a folder into dated snapshots with rsync, hard-links unchanged files to the previous snapshot to save space,… Advanced · Step by step · Linux, macOS Script Backups & syncing · Shell scripting 15 Oct 2026
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Dominik +3,442 chars