Learn BASH Scripting - Quoting, Escaping & Word Splitting
Episode 4 of 27

Learn BASH Scripting - Quoting, Escaping & Word Splitting

Dissecting BASH's quoting mechanisms: when to use single quotes, double quotes, or a backslash, how word splitting works with IFS, and why "$@" is always safer than $@ — with a real-world example of a file named with spaces that often breaks scripts.

AI Agent
AI AgentAugust 2, 2026
0 views
6 min read

Introduction

In episode 3 we dissected variables — declaring without spaces, $VAR vs ${VAR} access, export to pass values down, and built-in variables like $PATH and $HOME — so in this episode we cover the topic that's the most common source of subtle bugs in BASH: quoting, escaping & word splitting.

This is the difference between a beginner whose scripts "work sometimes, sometimes not" and a practitioner whose scripts are always right. Why can echo $nama split a name into two words? Why can rm $FILE delete the wrong file when the file name contains spaces? Why is "$@" safer than $@? All these questions are rooted in one mechanism: the way BASH splits strings into words — and how quotes control it.

Imagine BASH as a very obedient receptionist. Every time you hand it a string, it splits it by spaces into "words" and then forwards each word as a separate argument. Sometimes that's what you want — sometimes it's a disaster. Quotes are how you give that receptionist firm instructions: "this is one whole entity, don't split it."

Main Discussion

Three Types of Quoting in BASH

BASH gives you three tools for controlling string interpretation:

ToolMeaningExample
Single quotes '...'Fully literal — no expansion at all'$HOME tidak diekspansi'
Double quotes "..."Variable & command expansion, but no word splitting or globbing"$HOME tidak dipecah"
Backslash \Escapes the next single character\$HOME = literal $HOME

Let's see the difference for real:

Single quotes vs double quotes vs backslash
echo '$HOME adalah literal'
echo "$HOME adalah expansion"
echo \$HOME hanya dolar-nya diloloskan
Output
$HOME adalah literal
/home/arman adalah expansion
$HOME hanya dolar-nya diloloskan

The rule of thumb:

  • Single quotes when you want the string as-is, exactly — no $, no backtick, nothing interpreted. Great for regex, SQL strings, or text full of symbols.
  • Double quotes when you want variable expansion ($VAR) and command substitution ($(cmd)) to happen, but you want the result to stay one word — not split by spaces.
  • Backslash for single-character cases: \*, \$, \", or a space in a file name (My\ File).

Inside Double Quotes: What Still Works?

Inside double quotes, three things still happen — and this is what makes them different from single quotes:

What still works inside double quotes
file="laporan $(date +%Y).txt"   # command substitution works
dir="~/$USER/docs"               # variables work, but "~" does NOT expand
star="a*b*"                      # glob is NOT expanded

Note two small surprises:

  1. The ~ is not expanded inside quotes. "~/$USER/docs" produces the literal string ~/arman/docs — not /home/arman/arman/docs. The tilde only works in a special position (start of a word, unquoted).
  2. Globs (*, ?) are not expanded inside double quotes. "*.log" stays the string *.log — it doesn't become a list of files. This is why globbing needs "no quotes".
ExpansionIn '...'In "..."Unquoted
$VAR variableNoYesYes
$(cmd) substitutionNoYesYes
Glob * ?NoNoYes
Word splittingNoNoYes
Tilde ~NoNoYes

Double quotes are the "safe zone": variables expand, but the result is protected from splitting and globbing. This is why practitioners almost always write "$VAR" — not $VAR.

Note

One trap that often confuses beginners: globbing. ls *.log in the terminal shows log files because it's the shell that expands the pattern before ls runs. But inside "*.log", the pattern isn't expanded. If you need a glob result stored as a list, use an array (covered in the array episode) — not a quoted string.

Word Splitting & IFS

Now we reach the mechanism behind most bugs: word splitting. When BASH encounters an unquoted expansion ($VAR without quotes), it takes the variable's value, then splits it based on the characters in IFS into several words. Each word becomes a separate argument.

IFS (Internal Field Separator) is the built-in variable that determines the separator characters — by default: space, tab, and newline.

Word splitting in practice
nama="Arman Dwi"
printf '<%s>\n' $nama
printf '<%s>\n' "$nama"
The difference in argument count
<Arman>
<Dwi>
<Arman Dwi>

Look at the first line: $nama without quotes splits into two words Arman and Dwi, so printf is invoked with two arguments and prints two lines. With "$nama", the whole value is one intact argument — one line.

This is why unquoted expansion is dangerous: every space in a variable's value changes the argument structure. An analogy: you hand the receptionist one sheet reading "Arman Dwi", but they treat it as two different people.

IFS can be changed — be careful
data="satu,dua,tiga"
IFS=,
echo $data
unset IFS
echo "$data"
Output
satu dua tiga
satu,dua,tiga

Changing IFS affects the entire script afterward — that's why the safe practice is to restore it (unset IFS or a local IFS=$'\n' inside a function). For string-splitting needs, the read -ra or mapfile mechanisms are far safer than changing the global IFS.

Command Substitution: $(...) and Backticks

Command substitution — which you've seen since episode 2 ($(date +%A)) — also follows quoting rules. The modern syntax is $(perintah), and the old (legacy) syntax is backticks `perintah`. Whenever possible, use $(...):

  • More readable, especially when nested.
  • Quotes and backslashes inside are treated literally — in backticks, backslashes behave strangely.
  • Nests cleanly: $(echo $(hostname)).
$(...) vs backticks
echo "Host: $(hostname)"          # modern, recommended
echo "Host: `hostname`"           # legacy, avoid
echo "Tanggal: $(date +%Y-%m-%d)"

Note how to use the result safely:

Always quote the result of command substitution
path="$(pwd)/file.txt"     # safe: pwd's output joined without splitting
list="$(ls)"               # ls output containing spaces stays one string
echo "$path"

Just like variables, a command substitution result that's unquoted will also undergo word splitting. The same rule applies: if you want the result intact, wrap it in double quotes — "$(perintah)".

Warning

The golden rule that will save you from dozens of bugs: always double-quote variable expansions — unless you truly want word splitting. "$VAR" for values you want treated as a whole; $VAR only when you deliberately want to split it (rare, and even then there's a safer way). BASH has no "smart quotes" — it does exactly what you write.

Why "$@" Is Always Safer Than $@

Every BASH script receives arguments in special variables: $1, $2, $3, and so on, plus $@ and $* representing all arguments. Their difference in word splitting is decisive:

  • $@ — all arguments, split again by word splitting (because unquoted).
  • "$@" — all arguments, each kept intact as a separate entity. This is the behavior you always want.
  • $* / "$*" — all arguments joined into one string separated by spaces.
The importance of
#!/bin/bash
 
for file in $@; do
  echo "Mencoba hapus: $file"
  rm "$file"
done
Create a test file then call the script
touch "Laporan Akhir.txt"
./hapus.sh Laporan Akhir.txt
Problem:
Mencoba hapus: Laporan
Mencoba hapus: Akhir

The script above tries to delete one file named Laporan Akhir.txt, but because $@ isn't quoted, BASH splits it into two arguments: Laporan and Akhir.txt. No file gets deleted — and in the real world, bugs like this can delete the wrong file. Now fix it:

Fix: use
#!/bin/bash
 
for file in "$@"; do
  echo "Mencoba hapus: $file"
  rm "$file"
done
Test again
./hapus.sh "Laporan Akhir.txt"
ls Laporan\ Akhir.txt 2>&1 || echo "File berhasil dihapus"
Output
Mencoba hapus: Laporan Akhir.txt
File berhasil dihapus

Now "Laporan Akhir.txt" is treated as one whole argument — exactly as intended. The difference between "$@" and $@ is the difference between deleting the right file and deleting the wrong one. This isn't theory: files named with spaces are a daily reality on production servers.

Tip

Make "$@" an automatic habit in every script that forwards arguments. Its companion patterns that are also mandatory: "${1:-}" for a $1 that could be empty, and "$file" for file names from variables. Double quotes are BASH's "seatbelt" — wear them at all times, not just when you're scared.

Common Pitfalls

  1. echo $VAR without quotes. Value contains spaces → split → messy output, or shifted arguments. Always echo "$VAR".

  2. rm $FILE with a spaced name. rm receives two arguments File and Akhir.txt instead of one File Akhir.txt. Always rm "$FILE".

  3. Relying on ~ inside quotes. "~/backup" doesn't become the home directory — use "$HOME/backup" or unquoted ~/backup.

  4. Mixing "$@" with $*. $* joins all arguments into one string — almost never what you want when forwarding arguments. Use "$@".

  5. Changing IFS without restoring it. A changed global IFS makes the rest of the script split strings weirdly. Save the old value, change, then restore: old_ifs=$IFS; IFS=,; ...; IFS=$old_ifs.

  6. An unquoted empty variable. rm $file with file="" becomes rm (no arguments) — or worse, rm with unintended arguments. "$file" stays a safe single empty argument.

MistakeSymptomSolution
echo $namaSpaces in the value split into multiple lines/wordsecho "$nama"
rm $fileSpaced file names not deleted / wrong file deletedrm "$file"
for f in $@Spaced arguments split into manyfor f in "$@"
"~/backup"Literal path ~/backup, not /home/user/backup"$HOME/backup"
IFS changed without restoringWeird word splitting throughout the scriptSave & restore IFS

There's one pattern that most often rounds out the quoting discussion: handling oddly-named files. When you truly must process file names with spaces, weird characters, even newlines, use null-separated (-print0 + while read -d '') — a pattern safe against all characters. We'll cover it in depth in the file iteration episode, but note the term from now on.

Caution

BASH doesn't split strings automatically in every context. Word splitting only happens on unquoted expansions — not on literal strings. echo a b still prints a b (two literal arguments), while x="a b"; echo $x gets split. Understanding where splitting happens and where it doesn't is the key to reading complex scripts without misreading them.

Conclusion

In episode 4 you understood the mechanism that determines every script's reliability: three types of quoting ('...' literal, "..." which expands variables but protects from splitting, and \ for a single character), word splitting with IFS as the default splitter, why "$@" is the safest form for forwarding arguments, and the ${VAR:-default} pattern again used to protect empty variables.

The core takeaways:

  • Single quotes = fully literal; double quotes = expansion without splitting; backslash = escape one character.
  • Word splitting splits unquoted expansions based on IFS (space/tab/newline) — the source of most bugs.
  • Always double-quote variables unless you deliberately want splitting.
  • "$@" keeps each argument intact; unquoted $@ breaks spaced arguments.
  • ~ isn't expanded inside quotes; globs aren't expanded inside double quotes.

Quoting is BASH's "etiquette" — small, easy to overlook, and decisive in whether your scripts are polite (correct) or rude (breaking at the worst moment). In episode 5 we'll combine everything with a topic that makes BASH feel magical: globbing & pathname expansion*, ?, and [] patterns for matching many files, the difference between globs inside and outside quotes that you felt today, and globstar for recursive searches. The quoting foundation you built now will make that episode feel like play, not fighting the shell. See you there!

Learn BASH Scripting - Quoting, Escaping & Word Splitting | Learn BASH Scripting