Skip to content
Commitpedia
Log in Join & edit

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?

Edit history of Quoting and word splitting in bash: why “$var” and arrays save you: 1 recorded edit
Edit history · 1 edit
Type
Article
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Reading
2 min

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 awk or sed programs.
  • 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.

  1. Dominik +3,442 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?