Search code examples
bashshellposixkshbehavior

Shell behavior difference with empty parameter in heredocument


Ash, Dash or Bash, Zsh handles invalid empty parameters ${var:?} expansion error in a here-document differently.

Here is the experiment.sh code using genuine POSIX grammar:

#!/usr/bin/env sh

empty_var=

read -r value << EOF
Expected fail here ${empty_var:?}"
EOF
printf '$?=%d\nvalue=%s\n' $? "$value"

And here is the code to run the experiment with different shells:

for sh in ash bash dash ksh zsh; do
  printf 'Testing with: %s\n' "$sh"
  LC_ALL=C "$sh" ./experiment.sh || :
  echo
done

Obtained results:

Testing with: ash
./experiment.sh: 5: empty_var: parameter not set or null
$?=2
value=

Testing with: bash
./experiment.sh: line 5: empty_var: parameter null or not set

Testing with: dash
./experiment.sh: 5: empty_var: parameter not set or null
$?=2
value=

Testing with: ksh
./experiment.sh[5]: empty_var: parameter null
$?=1
value=

Testing with: zsh
./experiment.sh:5: empty_var: parameter not set

Bash and Zsh stops execution immediately while other shells continue the execution, just raising the return code $?.

What explanations are there, for this behavior difference with this genuine POSIX grammar construct?

Is it documented why Bash or Zsh choose to exit the script rather than return a failure code like Ksh, Dash or Ash?

Note that within other contexts like expansion in a string, all shells exit out of the script.

The behavior discrepancy occurs only in here-documents as far as I know.


Solution

  • The relevant POSIX documentation seems to be in the Parameter Expansion section of the Open Group Shell Command Language document.

    ${parameter:?[word]}
    Indicate Error if Null or Unset. If parameter is unset or null, the expansion of word (or a message indicating it is unset if word is omitted) shall be written to standard error and the shell exits with a non-zero exit status. Otherwise, the value of parameter shall be substituted. An interactive shell need not exit.

    My reading of it is that non-interactive shells are required to exit immediately. I can't find anything to suggest that it should behave differently in here-documents. It looks like you've found a bug that affects multiple shells.