11

The bash man page says the following about the read builtin:

The exit status is zero, unless end-of-file is encountered

This recently bit me because I had the -e option set and was using the following code:

read -rd '' json <<EOF
{
    "foo":"bar"
}
EOF

I just don't understand why it would be desirable to exit non successfully in this scenario. In what situation would this be useful?

7
  • 1
    This is one of a number of cases where a command will exit with a nonzero status because of something that isn't really an error (just maybe not a complete success) -- grep when it doesn't find a match, cmp and diff when the files don't match, even (( someexpression )) when the expression evaluates to 0 ("false" in arithmetic context). Dec 3, 2020 at 21:19
  • @GordonDavisson: I consider all of those things to be failures.
    – jesse_b
    Dec 3, 2020 at 21:49
  • 1
    @jesse_b That's just it, they're all debatable, depending on people's individual intuitions about what does and doesn't constitute a failure. Other people have certainly been surprised by these. To me, the read behavior does make sense, because it failed to read a complete record (including terminator) from its input. Dec 4, 2020 at 1:32
  • 3
    But these are all reasons why you should not use set -e routinely.
    – Barmar
    Dec 4, 2020 at 15:26

3 Answers 3

23

read reads a record (line by default, but ksh93/bash/zsh allow other delimiters with -d, even NUL with zsh/bash) and returns success as long as a full record has been read.

read returns non-zero when it finds EOF while the record delimiter has still not been encountered.

That allows you do do things like

while IFS= read -r line; do
  ...
done < text-file

Or with zsh/bash

while IFS= read -rd '' nul_delimited_record; do
  ...
done < null-delimited-list

And that loop to exit after the last record has been read.

You can still check if there was more data after the last full record with [ -n "$nul_delimited_record" ].

In your case, read's input doesn't contain any record as it doesn't contain any NUL character. In bash, it's not possible to embed a NUL inside a here document. So read fails because it hasn't managed to read a full record. It stills stores what it has read until EOF (after IFS processing) in the json variable.

In any case, using read without setting $IFS rarely makes sense.

For more details, see Understanding "IFS= read -r line".

0
9

This is one of the reasons I don't use set -e myself.

Now that you know that read will return 1 if it hits EOF without the given EOL delimeter, you can do one of:

# depending on the contents of the input, it's an error
# if no data was read:
IFS= read -rd '' json <<EOF || [[ -n $json ]]
...
EOF

# or, you don't care at all how much data was read
IFS= read -rd '' json <<EOF || :
...
EOF
0

The command read is oriented to lines coming from user input by default, but to make it work in files it must be capable of detect when the data in the file is exhausted before reaching an end of line delimiter.

In the specific case of your example, the word EOF is used to delimit the inline text of the redirection operator << and does not have any relation with the "end of file" indicator referred in read documentation, except in signaling tha interpreter that the data ends after the newline of the last line (with the closing curly brace). Of course, as the construct has complete data and does not come from a file, the situation where the end of file as an abrupt end of data condition cannot exist.

So the indication in the read's man page does not have actual application here, only if you were reading from a file or a data source which could mark the end as an eof (like the user pressing [Ctrl-D] or reaching the end of data in a pipe coming from another command).

This is compound with the special behaviour of the d and r options: -d causing to read everithing possible without regard to ends of lines, and (innecesary here, I suppose) -t causing read to ignore backslash escaped chars (that aren't present in the data and not needed as the text is inside quotes, except in the case where 'bar' should have some special character, like embedding binary data or unprintable chars), so this is clearly not intended for files as data sources.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .