13

Here's example that tries to execute command and checks if it was executed successfully, while capturing it's output for further processing:

#!/bin/bash

readonly OUTPUT=$(foo)
readonly RES=$?

if [[ ${RES} != 0 ]]
then
    echo "failed to execute foo"
    exit 1
else
    echo "foo success: '${OUTPUT}'"
fi

It reports that it was a success, even there is no such foo executable. But, if I remove readonly from OUTPUT variable, it preserves erroneous exit code and failure is detected.

I try to use readonly as for "defensive programming" technique as recommended somewhere... but looks like it bites itself in this case.

Is there some clean solution to preserve readonly while still capturing exit code of command/subshell? It would be disappointing that one has to remember this kind of exceptional use case, or revert to not using readonly ever...

Using Bash 4.2.37(1) on Debian Wheezy.

Vincas Dargis
  • 535
  • 5
  • 14

1 Answers1

13

The problem is that readonly is its own command and the exit code that it returns is its own exit code, not the exit code of the command substitution.

From help readonly:

Exit Status:
Returns success unless an invalid option is given or NAME is invalid.

So, you need to use two separate commands:

$ output=$(false)
$ readonly res=$?
$ readonly output

This saves the exit code that you want:

$ echo $res
1

Short of entering a debugger, there is no way to unset a readonly variable. So, don't set a variable readonly unless you want it to stay constant for the remainder of the bash session.

Variation

The two readonly commands can be combined into one (hat tip: Chepner):

$ output=$(false)
$ readonly output res=$?
$ echo $res
1

Aside

It is best practices to use lower- or mixed-case names for your variables. The system uses all upper-case names and you don't want to overwrite a system variable accidentally.

Community
  • 1
  • 1
John1024
  • 109,961
  • 14
  • 137
  • 171
  • 1
    To add to your aside on best practice, the `{ }` around `${RES}` are not performing any function. The `[[ ]]` are performing a textual comparison, to perform an arithmetic comparison use `if (( RES != 0 ))`. – cdarke Sep 26 '16 at 06:57
  • You can also set the read-only flag on both variables with the same command: `readonly res=$? output` – chepner Sep 26 '16 at 12:13
  • @chepner Very good. Answer updated to show a solution with a single readonly command. – John1024 Sep 26 '16 at 18:21