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.

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."
BASH gives you three tools for controlling string interpretation:
| Tool | Meaning | Example |
|---|---|---|
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:
echo '$HOME adalah literal'
echo "$HOME adalah expansion"
echo \$HOME hanya dolar-nya diloloskan$HOME adalah literal
/home/arman adalah expansion
$HOME hanya dolar-nya diloloskanThe rule of thumb:
$, no backtick, nothing interpreted. Great for regex, SQL strings, or text full of symbols.$VAR) and command substitution ($(cmd)) to happen, but you want the result to stay one word — not split by spaces.\*, \$, \", or a space in a file name (My\ File).Inside double quotes, three things still happen — and this is what makes them different from single quotes:
file="laporan $(date +%Y).txt" # command substitution works
dir="~/$USER/docs" # variables work, but "~" does NOT expand
star="a*b*" # glob is NOT expandedNote two small surprises:
~ 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).*, ?) 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".| Expansion | In '...' | In "..." | Unquoted |
|---|---|---|---|
$VAR variable | No | Yes | Yes |
$(cmd) substitution | No | Yes | Yes |
Glob * ? | No | No | Yes |
| Word splitting | No | No | Yes |
Tilde ~ | No | No | Yes |
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.
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.
nama="Arman Dwi"
printf '<%s>\n' $nama
printf '<%s>\n' "$nama"<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.
data="satu,dua,tiga"
IFS=,
echo $data
unset IFS
echo "$data"satu dua tiga
satu,dua,tigaChanging 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.
$(...) and BackticksCommand 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 $(...):
$(echo $(hostname)).echo "Host: $(hostname)" # modern, recommended
echo "Host: `hostname`" # legacy, avoid
echo "Tanggal: $(date +%Y-%m-%d)"Note how to use the result safely:
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.
"$@" 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.#!/bin/bash
for file in $@; do
echo "Mencoba hapus: $file"
rm "$file"
donetouch "Laporan Akhir.txt"
./hapus.sh Laporan Akhir.txtMencoba hapus: Laporan
Mencoba hapus: AkhirThe 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:
#!/bin/bash
for file in "$@"; do
echo "Mencoba hapus: $file"
rm "$file"
done./hapus.sh "Laporan Akhir.txt"
ls Laporan\ Akhir.txt 2>&1 || echo "File berhasil dihapus"Mencoba hapus: Laporan Akhir.txt
File berhasil dihapusNow "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.
echo $VAR without quotes. Value contains spaces → split → messy output, or shifted arguments. Always echo "$VAR".
rm $FILE with a spaced name. rm receives two arguments File and Akhir.txt instead of one File Akhir.txt. Always rm "$FILE".
Relying on ~ inside quotes. "~/backup" doesn't become the home directory — use "$HOME/backup" or unquoted ~/backup.
Mixing "$@" with $*. $* joins all arguments into one string — almost never what you want when forwarding arguments. Use "$@".
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.
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.
| Mistake | Symptom | Solution |
|---|---|---|
echo $nama | Spaces in the value split into multiple lines/words | echo "$nama" |
rm $file | Spaced file names not deleted / wrong file deleted | rm "$file" |
for f in $@ | Spaced arguments split into many | for f in "$@" |
"~/backup" | Literal path ~/backup, not /home/user/backup | "$HOME/backup" |
| IFS changed without restoring | Weird word splitting throughout the script | Save & 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.
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:
"$@" 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!