Why is set-o errexit breaking this read/heredoc expression?
read
returns a non-zero exit status if it doesn't find a delimiter, which is always the case when the delimiter is an empty string.
The exit code of the read command is 1 when the end of file (EOF) marker is reached. This will always happen when the delimiter -d
is null ''
in this special case where the source stream is a heredoc that could not contain a \0.
$ read -d '' message <<-_ThisMessageEnds_
> this is a
> multi line
> message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.
That the exit value is an error (not 0) makes the script exit could be avoided with a AND/OR construct:
read -d '' message <<-_ThisMessageEnds_ || echo "$message"
this is a
multi line
message
_ThisMessageEnds_
That will send the message to the console and yet avoid exiting it with errexit
.
But as we are on this path to reduce, why not use this directly:
cat <<-_ThisMessageEnds_
this is a
mulitline
message
_ThisMessageEnds_
No read command executed (more speed), no variable needed, no error from the exit code, less code to maintain.
read -d '' message
reads stdin until the first unescaped (as you didn't add -r
) NUL character or the end of the input and stores the data after $IFS
and backslash character processing into $message
(without the delimiter).
If no unescaped delimiter is found in the input, read
's exit status is non-zero. It only returns 0 (success) if a full, terminated record is read.
It's most useful for dealing with NUL-delimited records like the output of find -print0
(though you then need a IFS= read -rd '' record
syntax).
Here, you need to include a NUL delimiter in your here-document for read
to return successfully. That is however not possible with bash
which strips NUL characters from here-documents (that's at least better than yash
that strips everything past the first NUL, or ksh93 which seems to enter an infinite loop when a here-document contains a NUL).
zsh
is the only shell that can have a NUL in its here documents or store it in its variables or pass NUL characters in arguments to its builtins/functions. In zsh
, you can do:
NUL=$'\0'
IFS= read -d $NUL -r var << EOF
1
2
3$NUL
EOF
(zsh
also understand read -d ''
as a NUL delimiter like bash
. read -d $'\0'
also works in bash
but that does pass an empty argument to read
like in read -d ''
as bash
doesn't support NUL bytes in its command line).
(note that there's an extra newline character after that $NUL
)
In bash
, you can use a different character:
ONE=$'\1'
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF
But you could also do:
var=$(cat <<EOF
message
here
EOF
)
That will still not allow NUL characters. That's however standard code, so you don't need to rely on the zsh/bash specific read -d
. Also note that it removes all trailing newline characters, and except in ksh93
when the cat
builtin is enabled, that means spawning an extra process and command.