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 word

Passing Strings Safely to Commands

Quote variables embedded in command strings to treat them as literals:

  
user="guest"
grep "$user" /etc/passwd

This 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 string  

Glob 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 ls

Quotes 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 literally

Examples 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"
done

Function Arguments

myfunc() {
  echo "$1"
  cp "$2" "$3"
}

Leave a Reply

Your email address will not be published. Required fields are marked *