First, shell quoting is not like C strings. Just because you have a \n
between quotes does not put a newline in the actual string. It gets a bit confusing sometimes because some versions of echo
will interpret the backslash-en combination and output a newline instead. This behavior depends on the exact shell you are using, which options you pass to echo
, and which shell options are set, so it is not terribly reliable for cross-platform use. printf
is more standardized (it is either a shell builtin or an external program on most systems), but it is not present on (very?) old systems.
As Ignacio Vazquez-Abrams says, single quotes are one answer. The $''
variation he mentions at the end are not portable across all shells though (it is not in the POSIX standard for the shell language, and simpler shells do not support it (though ksh, bash, and zsh all do)).
You can even include literal line breaks in a single quoted string:
echo '#!/bin/env bash
python -m SimpleHTTPServer' >server.sh &&
chmod +x server.sh
Or, with printf
:
printf '#!/bin/env bash\npython -m SimpleHTTPServer\n' >server.sh &&
chmod +x server.sh
You should always take care with the first argument to printf
though, if there are any uspected %
characters, they will be interpreted as format specifiers (i.e. you should generally not use unknown strings as the first argument to printf
).
If you need no interpolation, use single quotes. They let you include literals that include anything except a single quote itself. If you need some C-like escape sequences, either put them in the first argument to printf
or use $''
(if your shell supports it).
If you need to include single quotes, you could use double quotes as the outermost quotes, but then you have to worry about what kinds of interpolations happens inside double quotes (varies by shell, but generally, \
, $
, and `
are all special and \
has a special meaning when it is immediately before a newline; !
is also special when history expansion is enabled in bash):
echo "The answer is '\$foo'\!"
Or, you could use concatenate multiple single quoted strings with escaped single quotes:
echo 'The answer is '\''foo'\''!'
Either way, it is not too pretty to think about or read if things get too complex.
When faced with wanting to include single quotes inside a literal string, you might change to using a quoted ‘here document’ instead:
cat <<\EOF
The answer is '$foo'!
EOF
The backslash before the EOF
makes the here document a literal one instead of the double-quote-ish one that you get without quoting the terminating word.
Here documents are easy to output (cat
) or to send to other commands as input, but they are not as convenient to use as arguments:
frob -nitz="$(cat <<\EOF
The answer is '$foo'!
EOF
)"
But if you need lots of literal data that includes both single quotes and characters that are special inside double quotes, such a construct can be a huge boon to readability (and writability). Note: as with all other uses of $()
, this will eat trailing newlines from the contents of the here document.
If you need trailing newlines, you can get them, but it is extra work:
s="$(cat <<\EOF
This will have leading and trailing newlines.
It takes as literal everything except a line with only "EOF" on it!$@#&\
(and the "EOF" bit can be changed by using a different word after <<)
The trailing 'xxx' will be trimmed off by the next command.
It is here to protect the otherwise trailing newlines that $() would strip off.
xxx
EOF
)"
s="${s%xxx}"
frob -nitz="$s"