In various bash scripts I have come across the following: $'\0'
An example with some context:
while read -r -d $'\0' line; do
echo "${line}"
done <<< "${some_variable}"
What does $'\0' return as its value? Or, stated slightly differently, what does $'\0' evaluate to and why?
It is possible that this has been answered elsewhere. I did search prior to posting but the limited number of characters or meaningful words in dollar-quote-slash-zero-quote makes it very hard to get results from stackoverflow search or google. So, if there are other duplicate questions, please allow some grace and link them from this question.
The exit code of the function (within the function) is set by using return . So when in a function return 0 is run, the function execution terminates, giving an exit code of 0.
$? is the exit status of the most recently-executed command; by convention, 0 means success and anything else indicates failure. That line is testing whether the grep command succeeded. The grep manpage states: The exit status is 0 if selected lines are found, and 1 if not found.
${} Parameter Substitution/Expansion A parameter, in Bash, is an entity that is used to store values. A parameter can be referenced by a number, a name, or by a special symbol. When a parameter is referenced by a number, it is called a positional parameter.
Success is traditionally represented with exit 0 ; failure is normally indicated with a non-zero exit-code. This value can indicate different reasons for failure. For example, GNU grep returns 0 on success, 1 if no matches were found, and 2 for other errors (syntax errors, non-existent input files, etc).
In bash, $'\0'
is precisely the same as ''
: an empty string. There is absolutely no point in using the special Bash syntax in this case.
Bash strings are always NUL-terminated, so if you manage to insert a NUL into the middle of a string, it will terminate the string. In this case, the C-escape \0
is converted to a NUL character, which then acts as a string terminator.
The -d
option of the read
builtin (which defines a line-end character the input) expects a single character in its argument. It does not check if that character is the NUL character, so it will be equally happy using the NUL terminator of ''
or the explicit NUL in $'\0'
(which is also a NUL terminator, so it is probably no different). The effect, in either case, will be to read NUL-terminated data, as produced (for example) by find
's -print0
option.
In the specific case of read -d '' line <<< "$var'
, it is impossible for $var
to have an internal NUL character (for the reasons described above), so line
will be set to the entire value of $var
with leading and trailing whitespace removed. (As @mklement notes, this will not be apparent in the suggested code snippet, because read
will have a non-zero exit status, even though the variable will have been set; read
only returns success if the delimiter is actually found, and NUL cannot be part of a here-string.)
Note that there is a big difference between
read -d '' line
and
read -d'' line
The first one is correct. In the second one, the argument word passed to read
is just -d
, which means that the option will be the next argument (in this case, line
). read -d$'\0' line
will have identical behaviour; in either case, the space is necessary. (So, again, no need for the C-escape syntax).
To complement rici's helpful answer:
Note that this answer is about bash
. ksh
and zsh
also support $'...'
strings, but their behavior differs:
* zsh
does create and preserve NUL (null bytes) with $'\0'
.
* ksh
, by contrast, has the same limitations as bash
, and additionally interprets the first NUL in a command substitution's output as the string terminator (cuts off at the first NUL, whereas bash
strips such NULs).
$'\0'
is an ANSI C-quoted string that technically creates a NUL (0x0
byte), but effectively results in the empty (null) string (same as ''
), because any NUL is interpreted as the (C-style) string terminator by Bash in the context of arguments and here-docs/here-strings.
As such, it is somewhat misleading to use $'\0'
because it suggests that you can create a NUL this way, when you actually cannot:
You cannot create NULs as part of a command argument or here-doc / here-string, and you cannot store NULs in a variable:
echo $'a\0b' | cat -v # -> 'a'
- string terminated after 'a'cat -v <<<$'a\0b' # -> 'a'
- dittoIn the context of command substitutions, by contrast, NULs are stripped:
echo "$(printf 'a\0b')" | cat -v # -> 'ab'
- NUL is stripped
However, you can pass NUL bytes via files and pipes.
printf 'a\0b' | cat -v # -> 'a^@b'
- NUL is preserved, via stdout and pipeprintf
that is generating the NUL via its single-quoted argument whose escape sequences printf
then interprets and writes to stdout. By contrast, if you used printf $'a\0b'
, bash
would again interpret the NUL as the string terminator up front and pass only 'a'
to printf
.If we examine the sample code, whose intent is to read the entire input at once, across lines (I've therefore changed line
to content
):
while read -r -d $'\0' content; do # same as: `while read -r -d '' ...`
echo "${content}"
done <<< "${some_variable}"
This will never enter the while
loop body, because stdin input is provided by a here-string, which, as explained, cannot contain NULs.
Note that read
actually does look for NULs with -d $'\0'
, even though $'\0'
is effectively ''
. In other words: read
by convention interprets the empty (null) string to mean NUL as -d
's option-argument, because NUL itself cannot be specified for technical reasons.
In the absence of an actual NUL in the input, read
's exit code indicates failure, so the loop is never entered.
However, even in the absence of the delimiter, the value is read, so to make this code work with a here-string or here-doc, it must be modified as follows:
while read -r -d $'\0' content || [[ -n $content ]]; do
echo "${content}"
done <<< "${some_variable}"
However, as @rici notes in a comment, with a single (multi-line) input string, there is no need to use while
at all:
read -r -d $'\0' content <<< "${some_variable}"
This reads the entire content of $some_variable
, while trimming leading and trailing whitespace (which is what read
does with $IFS
at its default value, $' \t\n'
).
@rici also points out that if such trimming weren't desired, a simple content=$some_variable
would do.
Contrast this with input that actually contains NULs, in which case while
is needed to process each NUL-separated token (but without the || [[ -n $<var> ]]
clause); find -print0
outputs filenames separated by a NUL each):
while IFS= read -r -d $'\0' file; do
echo "${file}"
done < <(find . -print0)
Note the use of IFS= read ...
to suppress trimming of leading and trailing whitespace, which is undesired in this case, because input filenames must be preserved as-is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With