Reputation: 3001
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!
Upvotes: 79
Views: 69197
Reputation: 10039
(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 syntactic special case of var=foo command...
(pretty confusing, semantically they are very different!). 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.
Upvotes: 5
Reputation: 123508
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 oncommand
's exit status, which is still in$?
.
This is not bash-specific. Quoting the end of section 2.9.1 "Simple Commands" in the "Shell & Utilities" volume of the The Open Group Base Specifications Issue 7, POSIX.1-2017 :
If there is no command name, but the command contained a command substitution, the command shall complete with the exit status of the last command substitution performed
Upvotes: 80
Reputation: 9871
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.
Upvotes: 0
Reputation: 403
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
.
Upvotes: 30
Reputation: 69
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
Upvotes: 5