Quoting Variables In Shell Scripts: To Quote Or Not To Quote
When to Quote Variables
Quoting variables in shell scripts is vital for strings containing spaces, special characters, and to prevent unwanted expansions. Quoting allows strings to be passed verbatim to commands and assignments without interference from the shell.
Strings with Spaces or Special Characters
Double quotes should surround strings containing spaces, tabs, or newlines to prevent word splitting. For example:
string="two words" echo "$string" # prints two words
Without quotes, the shell would expand this into two separate words. Quotes are also required for strings containing shell special characters like * or # to prevent globbing and interpretation.
Preventing Unwanted Expansions
Quotes prevent parameter expansions from occurring prematurely. Consider this example:
var="~*}" echo $var # tries to expand ~*} as a glob pattern echo "$var" # prints ~*}
The unquoted $var undergoes glob expansion and word splitting, whereas “$var” does not.
Passing Variables to Commands
Quotes must surround variable references passed as arguments to commands like grep, ssh, rsync to avoid word splitting or premature globbing:
search="search string" grep "$search" file.txt rsync -e "ssh -p 2222" $src $dest
How Quoting Works
Understanding how quoting works allows proper use for maximum control over variable expansions.
Single vs. Double Quotes
Double quotes allow variable expansions, command substitutions, and escapes for backslash and dollar sign. Variables and commands nested inside double quotes get evaluated. Single quotes perform no expansions or escapes except for one single quote which can be escaped with a backslash.
Escaping Quotes in Strings
To embed a literal quote inside a quoted string, escape it with a backslash:
echo "Quote: \" directly" # embeds " in string echo 'Quote: \' directly' # embeds ' in string
Valid vs. Invalid Expansions
Quoted strings undergo expansion up through the final quoting character. In an unclosed quoted string, expansions will continue to the end of the command.
echo "$var # $var expands echo "$var" # $var stopped at " quote
Quoting Variables in Assignments
Quoting variables during assignment stages the value for safe reuse.
Assigning Strings with Spaces
Enclose strings in quotes when assigning:
var="two words" echo $var # prints two words
Without quotes, the words would split on assignment.
Multi-line Strings
Embedded newlines can be added by escaping them or using multi-line quote syntax:
var="line 1 \ line 2" var=$(cat <Escaping Quotes and Special Characters
Quote special characters used later for glob patterns, regex matches, or literals:
pattern="*.txt" string="He said, \"hello\""Quoting Variables in Command Substitutions
Command substitutions should generally be quoted to prevent split results or mishandled argument strings.
Preventing Split Words
Quote command substitutions to prevent word splitting:
files=($(ls)) # splits output files=("$(ls)") # preserved as single wordPassing Strings Safely to Commands
Quote variables embedded in command strings to treat them as literals:
user="guest" grep "$user" /etc/passwdThis passes $user safely to grep without interpreting special characters.
Common Gotchas
Failure to quote properly in key contexts commonly leads to bugs.
Unquoted Expansions in if Statements
Never leave expansions unquoted in test commands:
if [ $var = "hello" ] # splits value of $var if [ "$var" = "hello" ] # compares literal stringGlob Patterns in Unquoted Variables
Glob patterns store in variables expand too early if unquoted:
pattern=*.txt # expands immediately ls $pattern # useless, passes literal *.txt ls "$pattern" # passes *.txt to lsQuotes Eaten by Commands
Some commands strip quotes from arguments. Use escapes to protect them:
grep "$var" file # quotes removed grep \"$var\" file # escaped quotes passed literallyExamples of Proper Quoting
Examples of quoting variables properly in typical shell scripting contexts.
Assignments
var="some string" var="var-$random-$(date)" path="/home/$user/docs/"Conditional Tests
[ "$var" = "value" ] [[ "$var" =~ "^[0-9]+$" ]] [ -f "$file" ]Command Substitutions
output="$(ls -l)" files=($(find . -type f))Loops
for file in "$@" do grep "$pattern" "$file" doneFunction Arguments
myfunc() { echo "$1" cp "$2" "$3" }