read -d
changes the character that stops the read from the default newline to the first character of the following argument.
The important thing to understand is that bash uses C strings, which are terminated by literal NULs. Thus, when the following argument is ''
, the first (and only) character is the NUL terminating it; thus, when the shell dereferences the char*
to get the first character it points to, it gets a NUL.
Now, when you redirect a heredoc with <<EOF
, that document won't actually have any NULs in it -- so how does your code work?
The answer is that your code expects the read
operation to fail. Even when it fails, read
still populates its destination variable; so if you don't have a terminating delimiter, read
has a nonzero exit status... but it still puts all the data you wanted to collect in the variable anyhow!
For a version that doesn't trigger set -e
errors, consider checking whether the destination variable is empty after the read is complete:
{ IFS= read -r -d '' string || [[ $string ]]; } <<'EOF'
...string goes here...
EOF
What are the changes we made?
IFS=
prevents leading or trailing whitespace (or other characters, should IFS have been redefined) from being stripped.
read -r
prevents content with backslash literals from being mangled.
|| [[ $string ]]
means that if read
reports a failure, we then check whether the string was populated, and still consider the overall command a success should the variable be non-empty.