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
> 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:
Quotes in quotes
Sometimes it is necessary to have a set of quotes within quotes. A common situation for MacAdmins is the following
osascript -e 'display dialog "Hello, World"'
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.
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
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