9

The following code will cause the 'if' statement to exit early and not execute the 'echo' command in the 'if' block. I am wondering why this happens only in the 'if' block but not in the main part of the script. Note: I understand changing ':=' to ':-' will solve the problem - I am not looking to fix the problem, I am looking to understand the difference between the execution environment of the 'if' block that causes it to happen in the first place.

#!/bin/bash

if true; then
        VAR=${$1:='val'}
        echo "This does not run"
fi


VAR=${$1:='val'}
echo "This does run"

The output is

line 4: ${$1:='val'}: bad substitution
line 7: ${$1:='val'}: bad substitution
This does run

Again - I am not interested in fixing the bad substitution error message, I understand how to do that and why it happens. What I want to understand is why the echo "This does not run" line does not run when there is a bad substitution above it in an 'if' block.

Reproduced on the following bash versions:

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

  • I can reproduce the behavior with `GNU bash, version 5.1.16(1)-release (x86_64-pc-msys)` and `GNU bash, version 5.0.3(1)-release (arm-unknown-linux-gnueabihf)` – Bodo Dec 16 '22 at 16:49
  • Just so you know, you should post the error message too. – Constantin Hong Dec 16 '22 at 17:09
  • @ConstantinHong thanks for the suggestion - in this case I am more interested in the output than the error but I added the output as suggested – Thomas Giunta Dec 16 '22 at 17:59
  • You should always post the errors, simply so we don't get distracted when we test and see "ooh, look, there's an error! Maybe that's the problem". So if you show it up front, you save everyone time. – terdon Dec 16 '22 at 18:00
  • 1
    Here's something interesting: try putting the variable assignment in a subshell. Like this: `if true; then ( VAR=${$1:='val'} ); echo "This does not run"; fi`. That makes it run the `echo`. – terdon Dec 16 '22 at 18:01
  • It seems **bad substitution** exits from *code blocks* (and sub-shells if they exist). If you put the whole code between `{}` then the script will not *echo* anything, only the error message: **bad substitution** will be displayed. However if you use `{}` in the first `VAR=${$1:='val'}` like this: `{ VAR=${$1:='val'}; }` the **bad substitution** error will exit from `if` statement and not from the current *curly braces block*. – Edgar Magallon Dec 16 '22 at 18:49
  • Btw, this seems to happen only when there is a syntax error in the code, e.g. a `bad substitution`. Since `${$1:='val'}` is not correct you should use this syntax: `${1:='val'}` (without `$` in `$1`). However if you use `${1:='val'}` you will get another syntax error: **cannot assign in this way** (this because you are using positional parameters and not a variable name) which will cause the same behavior you have. – Edgar Magallon Dec 16 '22 at 19:52
  • This is similar, but not the same as, what's described here: [Setting bash options in a compound command](https://unix.stackexchange.com/q/522511) (I think...) – Kusalananda Dec 16 '22 at 20:01
  • Actually what's interesting is why does execution continue at all after line 4 - zsh, yash, mksh and dash exit immediately. – Arkadiusz Drabczyk Dec 16 '22 at 20:13
  • @Quasímodo: I don't know if it's a bug or a feature, Bash exits too when it's invoked as sh – Arkadiusz Drabczyk Dec 16 '22 at 20:27
  • But why are you showing me this? Bash does more than POSIX tells it to. – Arkadiusz Drabczyk Dec 16 '22 at 20:29
  • I don't know if it _can_, or _should_, but it does and has for years `30. Non-interactive shells exit if a parameter expansion error occurs.` https://tiswww.case.edu/php/chet/bash/POSIX (the link to that page is included in the manpage) – Arkadiusz Drabczyk Dec 16 '22 at 20:39

1 Answers1

3

As Kusalananda pointed in comments, this is essentially the same as described in setting Bash options in a compound command.

From Bash reference:

The following is a brief description of the shell’s operation when it reads and executes a command. Basically, the shell does the following:

  1. Parses the tokens into simple and compound commands (see Shell Commands).

  2. Performs the various shell expansions (see Shell Expansions), breaking the expanded tokens into lists of filenames (see Filename Expansion) and commands and arguments.

  3. ...

  4. Executes the command (see Executing Commands).

The block in the if statement is a compound command and has an expansion error, namely VAR=${$1:='val'}, so the whole chunk fails, step 6 is never reached and the first echo is never executed.

Note that this particular handling of expansion errors violates the POSIX standard, that in section 2.8.1 says that an expansion error shall exit a non-interactive shell.

In POSIX mode, Bash does operate in a conformant manner:

% bash horse 
horse: line 2: ${$1:='val'}: bad substitution
horse: line 4: ${$1:='val'}: bad substitution
This does run
% bash --posix horse 
horse: line 2: ${$1:='val'}: bad substitution

To acquit Bash, this violation is documented:

The following list is what’s changed when ‘POSIX mode’ is in effect:

  1. A non-interactive shell exits with an error status if a variable assignment error occurs when no command name follows the assignment statements.
Quasímodo
  • 18,625
  • 3
  • 35
  • 72