Home > Enterprise >  Exit status of `read` is 1 even though it appears to be succeeding
Exit status of `read` is 1 even though it appears to be succeeding

Time:07-25

read -r -d '' TMP <<<'hello'

successfully writes "hello" to $TMP, yet the exit status is 1.

Is this expected/intended? Should I just put ! at the start of that line to invert the exit code if I want to to succeed when it succeeds?


N.B. the above example is simplified. Here's the full thing:

IFS='' read -r -d '' SLACK_PAYLOAD <<'JSON'
{
    "channel": $channel,
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $message
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $changelog
            }
        }
    ]
}
JSON

I'm mostly using read because I want multiple lines.


To be even more clear, my script is using

#!/usr/bin/env -S bash -eio pipefail

It appears there are some subtle differences with zsh which I'm not really opposed to (I use it as my shell but not for scripts), but my script is already written for bash.

CodePudding user response:

read expects the line to end with the configured delimiter and errors out if it's missing. You can see this even when not using -d if you have it read input that's missing the usual newline at EOF.

How can we pass input without it? Well, <<< automatically adds a newline, so that won't work. We can omit it by using

  • printf, which doesn't add newlines the way <<< (or echo) does; and
  • process substitution to pass printf's output to read without creating a subshell. If we used a pipeline like printf | read then read would run in a subshell and the parent shell wouldn't see the assignment to TMP.

That looks like:

bash❯ read -r TMP < <(printf 'hello')
bash❯ echo $?
1

Now when you use -d '', read looks for a \0 (NUL character) delimiter. Of course there is no \0 at the end of 'hello'. So let's add one! If we do then it succeeds:

bash❯ read -r -d '' TMP < <(printf 'hello')
bash❯ echo $?
1
bash❯ read -r -d '' TMP < <(printf 'hello\0')
bash❯ echo $?
0

Interestingly, in zsh process substitution isn't needed:

zsh❯ read -r -d '' TMP <<< $'hello'
zsh❯ echo $?
1
zsh❯ read -r -d '' TMP <<< $'hello\0'
zsh❯ echo $?
0

If your intention is to set a variable to a large multiline string there's no need for read. Strings can span multiple lines:

SLACK_PAYLOAD='
{
    "channel": $channel,
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $message
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $changelog
            }
        }
    ]
}
')

Or if you like the heredoc syntax, this version with cat would also work:

SLACK_PAYLOAD="$(cat <<'JSON'
{
    "channel": $channel,
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $message
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": $changelog
            }
        }
    ]
}
JSON
)"

CodePudding user response:

Is this expected/intended?

Yes.

Exit Status: The return code is zero, unless end-of-file is encountered, read times out (in which case it's greater than 128), a variable assignment error occurs, or an invalid file descriptor is supplied as the argument to -u.

(from read --help, emphasis mine)

In your case, it seems like you don't need read at all, you could just do

TMP='hello'
  •  Tags:  
  • bash
  • Related