Exit code of variable assignment to command substitution in Bash

LinuxBashShellExit Code

Linux Problem Overview


I am confused about what error code the command will return when executing a variable assignment plainly and with command substitution:

a=$(false); echo $?

It outputs 1, which let me think that variable assignment doesn't sweep or produce new error code upon the last one. But when I tried this:

false; a=""; echo $?

It outputs 0, obviously this is what a="" returns and it override 1 returned by false.

I want to know why this happens, is there any particularity in variable assignment that differs from other normal commands? Or just be cause a=$(false) is considered to be a single command and only command substitution part make sense?

-- UPDATE --

Thanks everyone, from the answers and comments I got the point "When you assign a variable using command substitution, the exit status is the status of the command." (by @Barmar), this explanation is excellently clear and easy to understand, but speak doesn't precise enough for programmers, I want to see the reference of this point from authorities such as TLDP or GNU man page, please help me find it out, thanks again!

Linux Solutions


Solution 1 - Linux

Upon executing a command as $(command) allows the output of the command to replace itself.

When you say:

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

the output produced by the command false is stored in the variable a. Moreover, the exit code is the same as produced by the command. help false would tell:

false: false
    Return an unsuccessful result.
    
    Exit Status:
    Always fails.

On the other hand, saying:

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

causes the exit code for the assignment to a to be returned which is 0.


EDIT:

Quoting from the manual:

> If one of the expansions contained a command substitution, the exit > status of the command is the exit status of the last command > substitution performed.

Quoting from BASHFAQ/002:

> How can I store the return value and/or output of a command in a > variable? > > ... > > output=$(command) > > status=$? > > The assignment to output has no effect on command's exit status, which > is still in $?.

Solution 2 - Linux

Note that this isn't the case when combined with local, as in local variable="$(command)". That form will exit successfully even if command failed.

Take this Bash script for example:

#!/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 is the output of this:

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

This is because local is actually a builtin command, and a command like local variable="$(command)" calls local after substituting the output of command. So you get the exit status from local.

Solution 3 - Linux

I came across the same problem yesterday (Aug 29 2018).

In addition to local mentioned in Nick P.'s answer and @sevko's comment in the accepted answer, declare in global scope also has the same behavior.

Here's my Bash code:

#!/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

Note the values of local_ret2 and global_ret2 in the output above. The exit codes are overwritten by local and declare.

My Bash version:

$ echo $BASH_VERSION 
4.4.19(1)-release

Solution 4 - Linux

(not an answer to original question but too long for comment)

Note that export A=$(false); echo $? outputs 0! Apparently the rules quoted in devnull's answer no longer apply. To add a bit of context to that quote (emphasis mine):

> 3.7.1 Simple Command Expansion > > ... > > If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits. If one of the expansions contained a command substitution, the exit status of the command is the exit status of the last command substitution performed. If there were no command substitutions, the command exits with a status of zero. > > 3.7.2 Command Search and Execution [ — this is the "below" case]

IIUC the manual describes var=foo as special case of var=foo command... syntax (pretty confusing!). The "exit status of the last command substitution" rule only applies to the no-command case.

While it's tempting to think of export var=foo as a "modified assignment syntax", it isn't — export is a builtin command (that just happens to take assignment-like args).

=> If you want to export a var AND capture command substitution status, do it in 2 stages:

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

This way also works in set -e mode — exits immediately if the command substitution return non-0.

Solution 5 - Linux

As others have said, the exit code of the command substitution is the exit code of the substituted command, so

FOO=$(false)
echo $?
---
1

However, unexpectedly, adding export to the beginning of that produces a different result:

export FOO=$(false)
echo $?
---
0

This is because, while the substituted command false fails, the export command succeeds, and that is the exit code returned by the statement.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionReorxView Question on Stackoverflow
Solution 1 - LinuxdevnullView Answer on Stackoverflow
Solution 2 - LinuxNick P.View Answer on Stackoverflow
Solution 3 - LinuxZhi ZhuView Answer on Stackoverflow
Solution 4 - LinuxBeni Cherniavsky-PaskinView Answer on Stackoverflow
Solution 5 - LinuxSpongmanView Answer on Stackoverflow