Quoting strings and variable substitutions is a bit of a dark art in shell scripts. It looks simple and straightforward enough, but there are lots of small devils in the details, that can come out and haunt you.
Basics: why we quote strings
In shell scripts (sh, bash, and zsh) you use the equals character =
to assign a string value to a variable:
> name=John
> dirpath=/Library
As long as there are no special characters in the literal string, there is no need to quote the string.
When you use the variable, you prefix a $
symbol:
> echo $name
John
> cd $dirpath
> pwd
/Library
When the literal string contains special characters, you need to either escape the special characters with the backslash \
or quote the entire string with either single quotes '
or double quotes "
. Space is proverbial ‘killer character’, especially for file paths. (More details in this post.)
name='John Doe'
dirpath="/Library/Application Support"
The difference between single quotes and double quotes is important. Single quotes escape every special character except the single quote itself. A single quoted string of '#$"\!'
will represent exactly those characters.
Double quotes escape most characters, except the double quote "
the backtick `, the dollar sign $
, the backslash \
, and the exclamation mark !
. (There are slight differences between the shells on this.)
This allows us to use old-style command substitution with backticks and variable substitution (dollar sign) within double quoted strings:
> echo "Hello, $name"
Hello, John Doe
> echo "The Computer Name is `scutil --get ComputerName`"
Though you should be using the $(…)
syntax for command substitution instead of backticks `. The parenthesis syntax is more readable and can be nested.
In general, it is a good rule to always quote literal strings. Whether you should use double quotes or single quotes depends on the use case.
Combining literal strings with special characters
Things can start getting complicated when you want special characters with their special functionality. For example, when you want to refer to the path ~/Library/Application Support
, you should put it in quotes, because of the space. But when you put the ~
in the quotes, it will not be substituted to the user’s home directory path.
There are a few ways to solve this problem. You could escape the space with a backslash. You could use the $HOME
variable instead (but be sure you are in a context where this is set). But the easiest is to move the special character out of the quotes:
dirpath=~"/Library/Application Support"
Quotes in quotes
Sometimes it is necessary to have a set of quotes within quotes. A common situation for MacAdmins is the following osascript
:
osascript -e 'display dialog "Hello, World"'
The osascript
command can be used to run Apple commands or scripts. Since AppleScript uses double quotes for literal strings, the entire AppleScript command is passed in single quotes. This keep the command string together and the double quotes in single quotes don’t confuse the shell.
This works fine, until you want to do something like this:
computerName=$(scutil --get ComputerName)
newName=$(osascript -e 'text returned of (display dialog "Enter Computer Name" default answer "$computerName")')

Again, we put the AppleScript command in single quotes, so we can use double quotes inside. But now, the single quotes are also blocking the variable substitution and we get the literal $computerName
in the dialog.
There are a few solutions out of this, I will demonstrate three:
First, you could close the single quotes before the variable substitution and re-open them after:
osascript -e 'text returned of (display dialog "Enter Computer Name" default answer "'$computerName'")'
This will in this form as long as $computerName
contains no spaces. This is unlikely as the default computer name is something like Armin's MacBook Pro
. The shell will consider that space a separator before a new argument, breaking the AppleScript command into meaningless pieces and failing the osascript
command. We can avoid that by putting the substitution itself in double quotes:
osascript -e 'text returned of (display dialog "Enter Computer Name" default answer "'"$computerName"'")'
This works and is entirely legal syntax, but not very legible.
Escaping the escape characters
Another solution is to use double quotes for the entire AppleScript command, we can use variable substitution inside. But then we have to deal with the double quotes required for the AppleScript string literal. The good news here is that we can escape those with the backslash:
osascript -e "text returned of (display dialog \"Enter Computer Name\" default answer \"$computerName\")"
This doesn’t win prizes for legibility either, but I consider it an improvement over the previous approach.
Here Docs
The above approaches with work in sh, bash, and zsh. But bash and zsh have another tool available that can work here. The ‘here doc’ syntax can be used to include an entire block of AppleScript code in a bash or zsh script:
#!/bin/bash
computerName=$(scutil --get ComputerName)
newName=$(osascript <<EndOfScript
text returned of (display dialog "Enter Computer Name" default answer "$computerName")
EndOfScript
)
echo "New name: $newName"
The syntax is a bit weird. The <<EndOfScript
says: take all the text until the next appearance of EndOfScript
and pipe it into the preceding command, in this case osascript
.
The ‘marker’ EndOfScript
is entirely arbitrary. Many people choose EOF
but I prefer something a little more descriptive. Whatever label you choose the ending marker has to stand alone in its line. This is why the parenthesis )
which closes the command substition $(
has to stand alone in the next line.
You can still use variable substitution in a here doc, so the variable $computerName
will be substituted before the here doc is piped into osascript
.