Single/double quotes vs. backslash: single quotes and backslashes are equivalent in quoting power. It's a lot more convenient to use single quotes to quote a long string with spaces, tabs, newlines, ()[]*$><?|{}~&;"`^!#
and probably other characters I'm forgetting. But you could achieve exactly equivalent results with just backslashes (beware of the overloading of backslashes within backticks (`...`
) though)
Double quotes are unique, though. $
expands inside double-quotes, but not single. "$foo" expands foo, but protects the expanded result from word-splitting and glob expansion.
http://mywiki.wooledge.org/BashFAQ might be a good place to start. The bash manual doesn't spend much time on how to use all the features it describes, just how they work individually.
It's literally not possible to pass a string containing a zero-byte as a command line argument, or to a system call. The ABI (application binary interface) that specifies exactly how data is passed between processes and the kernel uses C strings for everything (except binary data), including command line arguments and file / path arguments to system calls. C strings are character arrays where the end-of-string is marked by a zero-byte. There is no way to "escape" a zero byte to indicate that it's not the end of a string.
Any attempt to do something like touch $'foo\0bar'
would just result in touch
seeing its argument list as
argv[0] = "/bin/touch";
argv[1] = "foo";
Even though sitting in memory, argv[1] = "foo\0bar\0"
, the first \0
marks the end of the string. Actually, "foo\0bar\0" wouldn't make it as far as the new process's argv. It wouldn't make it out of the argv array in the exevce(2)
system call that ran touch
.
And even if you wrote a C or perl program with character arrays / strings containing null bytes, passing them to a system call like open(2)
would cause the same interpretation of the string by kernel. System calls that need to handle arbitrary data, like read(2)
and write(2)
, take a length argument as well as a pointer to the buffer.
It's not even possible to do much of anything with null bytes with bash. As jimmij points out, the bash syntax for writing a string literal with escape-sequence processing is $'string'
, but writing a \0
in your string literal acts as a string terminator inside bash. I guess this means bash stores strings internally as C strings, not with an explicit length.
str=$'foo\0bar'
echo "${#str}" # 3, showing that bash isn't even storing it in a variable.
echo "$str" | wc -c # 4. wouldn't work even if ${#str} did: echo's cmdline would eat it
wc -c <<< $'foo\0bar' # 4 (includes a newline)
So we can't use this syntax to send a null byte anywhere. We'd have to use tr
or something.
However, bash printf
does have some limited support for \0
. The %b
conversion processes backslash escapes, including \0
. And printf
already processes such escapes in its format string.
printf '\0'
prints a zero byte. Pipe it into hexdump -C
to confirm.
printf '%s\0%s' foo bar | hexdump -C
writes foo.bar
(where . is a NUL byte) to stdout. Note that \0
inside single or double quotes doesn't expand on its own; only $'\0'
quoting would expand it before printf, which would make it act as a terminator.
printf '%b' 'foo\0bar'
does the same thing, but more complicated.
NUL
character you were looking for ('\0'
is from C).