Coder Perfect

Variable assignment to command substitution exit code in Bash

Problem

When I execute a variable assignment explicitly and with command substitution, I’m not sure what error code the command will return:

a=$(false); echo $?

It returns 1, which leads me to believe that variable assignment does not sweep or generate a new error code after the previous one. But here’s what happened when I tried it:

false; a=""; echo $?

It returns 0 because that’s what a=”” does, and it overrides the 1 provided by false.

I’m curious as to why this occurs; is there something unique about variable assignment that sets it apart from other commands? Or is it just because a=$(false) is regarded as a single command, and only the command replacement part makes sense?

— UPDATE —

Thanks to everyone who responded; I understood “When you assign a variable using command replacement, the exit status is the command’s status” from the responses and comments. (by @Barmar), this explanation is excellently clear and easy to comprehend, but the language isn’t precise enough for programmers; I’d want to see a reference to this point from authority such as the TLDP or the GNU man page; please assist me in finding it; thank you!

Asked by Reorx

Solution #1

When you run a command as $(cmd), the command’s output will replace itself.

When you say:

a=$(false)             # false fails; the output of false is stored in the variable a

The variable an is used to store the output of the command false. Furthermore, the exit code is the same as the command’s output. False assistance might state:

false: false
    Return an unsuccessful result.

    Exit Status:
    Always fails.

Saying, on the other hand:

$ false                # Exit code: 1
$ a=""                 # Exit code: 0
$ echo $?              # Prints 0

causes the assignment to a to return the exit code 0, which is 0.

EDIT:

The following is a quote from the manual:

Quoting from BASHFAQ/002:

Answered by devnull

Solution #2

Note that when paired with local, as in local variable=”$(command),” this isn’t the case. Even if the command fails, the form will leave successfully.

Consider the following Bash script:

#!/bin/bash

function funWithLocalAndAssignmentTogether() {
    local output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

function funWithLocalAndAssignmentSeparate() {
    local output
    output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

funWithLocalAndAssignmentTogether
funWithLocalAndAssignmentSeparate

Here’s what you’ll get as a result of this:

nick.parry@nparry-laptop1:~$ ./tmp.sh 
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1

This is due to the fact that local is a builtin command, and a command like local variable=”$(command)” calls local after substituting command’s output. As a result, you obtain the exit status from local.

Answered by Nick P.

Solution #3

Yesterday, I ran through the same issue (Aug 29 2018).

Declare in global scope has the same behaviour as local, as noted in Nick P.’s answer and @sevko’s comment in the accepted answer.

My Bash code is as follows:

#!/bin/bash

func1()
{
    ls file_not_existed
    local local_ret1=$?
    echo "local_ret1=$local_ret1"

    local local_var2=$(ls file_not_existed)
    local local_ret2=$?
    echo "local_ret2=$local_ret2"

    local local_var3
    local_var3=$(ls file_not_existed)
    local local_ret3=$?
    echo "local_ret3=$local_ret3"
}

func1

ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"

declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"

declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"

The output:

$ ./declare_local_command_substitution.sh 2>/dev/null 
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2

In the output above, take note of the values of local ret2 and global ret2. The local and declare commands overwrite the exit codes.

My Bash version:

$ echo $BASH_VERSION 
4.4.19(1)-release

Answered by Zhi Zhu

Solution #4

(Not a response to the original question, but it’s too long to comment on)

It’s worth noting that export A=$(false); echo $? returns 0! The rules mentioned in devnull’s response don’t seem to apply any longer. To put the quote in context (emphasis mine), consider the following:

According to the instructions, var=foo is a particular instance of the var=foo command… syntax (which is a bit confusing!). Only the no-command case is covered by the “exit status of the last command substitution” rule.

While it’s tempting to think of export var=foo as “modified assignment syntax,” it’s not — it’s a built-in command (that just happens to take assignment-like args).

=> Exporting a var AND capturing command substitution status should be done in two stages:

A=$(false)
# ... check $?
export A

This method also works in set -e mode, exiting immediately if the command replacement returns a value other than zero.

Answered by Beni Cherniavsky-Paskin

Post is based on https://stackoverflow.com/questions/20157938/exit-code-of-variable-assignment-to-command-substitution-in-bash