Search code examples
bash

read fails with set -euo pipefail


I have script like this

#!/bin/bash

# exit when any command fails
set -euo pipefail

api_key="${POWERDNS_API_KEY}"
dns_server_addr="127.0.0.1"
server_id="localhost"
zone_id="${K8S_FQDN_SUFFIX}."
prefix="$1"
ip="$2"

read -rd '' payload << EOF
{
    "rrsets": [
        {
            "name": "$prefix.svc.${K8S_FQDN_SUFFIX}.",
            "type": "A",
            "changetype": "REPLACE",
            "ttl": 10,
            "records": [
                {
                    "content": "$ip",
                    "disabled": false
                }
            ]
        }
    ]
}
EOF

curl -i -H "X-API-Key: $api_key" -X PATCH --data "$payload" \
"http://$dns_server_addr:8081/api/v1/servers/$server_id/zones/$zone_id"

It fails on read -rd '' payload without message. If I remove set -euo pipefail everything works fine. What I do wrong? Actually I don't even need set -euo pipefail, just interesting why this happens.


Solution

  • The read command tries to read a single line from standard input. If it fails for any reason -- including hitting the end-of-file before seeing a line terminator -- it exits with a nonzero status. In your case, the -d '' option tells read to look for an ASCII null character as the line terminator, and since the here-document doesn't have one it reads until it hits EOF... and then exits with an error status.

    read's behavior may be a bit counterintuitive here. It read something successfully, and set the variable (payload) just fine; but since it hit EOF it's required by the POSIX standard to return an error. The exact same thing happens when reading a text file line-by-line, and the last line isn't terminated.

    Normally this wouldn't be a big deal, but the -e option to set makes the shell exit if any simple command exits with a nonzero (error) status (with lots of messy exceptions that aren't relevant here). When read does that, the script exits on the spot.

    So, there are a few possible solutions. You could just not use set -e, you could put set +e just before the read command (and maybe set -e again afterward), or you could make it a compound command that'll succeed, like this:

    read -rd '' payload << EOF || true
    ...
    

    (Here, the || means if the first command fails it'll run the second, and true always succeeds, so the compound command is considered to succeed.)