10

I've found the strange behaviour for me, which I can't explain. The following code is work OK:

function prepare-archive {
blah-blah-blah...
_SPEC_FILE=$(check-spec-file "$_GIT_DIR/packaging/")
exit $?
blah-blah-blah...
}

means I get value which I expect:

bash -x ./this-script.sh:
++ exit 1
+ _SPEC_FILE='/home/likern/Print/Oleg/print-service/packaging/print-service.spec
/home/likern/Print/Oleg/print-service/packaging/print-service2.spec'
+ exit 1

As soon as I add local definition to variable:

local _SPEC_FILE=$(check-spec-file "$_GIT_DIR/packaging/")

I get following:

bash -x ./this-script.sh:
++ exit 1
+ local '_SPEC_FILE=/home/likern/Print/Oleg/print-service/packaging/print-service.spec
/home/likern/Print/Oleg/print-service/packaging/print-service2.spec'
+ exit 0
$:~/MyScripts$ echo $?
0

Question: Why? What has happened? Can I catch output from subshell to local variable and check subshell's return value reliably?

P.S.: prepare-archive is called in the main shell script. The first exit is the exit from check-spec-file function, the second from prepare-archive function - this function itself is executed from main shell script. I return value from check-spec-file by exit 1, then pass this value to exit $?. Thus I expect they should be the same.

Mephi_stofel
  • 355
  • 2
  • 6
  • 15
  • In what context is `prepare-archive` called? The `++ exit 1` doesn't fit with any code you've shown. – chepner Oct 11 '12 at 13:35

3 Answers3

24

To capture subshell's exit status, declare the variable as local before the assignment, for example, the following script

#!/bin/sh

local_test()
{
    local local_var
    local_var=$(echo "hello from subshell"; exit 1)
    echo "subshell exited with $?"
    echo "local_var=$local_var"
}

echo "before invocation local_var=$local_var in global scope"
local_test
echo "after invocation local_var=$local_var in global scope"

produces the following output

before invocation local_var= in global scope
subshell exited with 1
local_var=hello from subshell
after invocation local_var= in global scope
vilpan
  • 596
  • 5
  • 13
  • Would you mind explaining what's happening here? Your answer fixed my problem, but I still don't understand how and why it does so. – Ihor Kaharlichenko Nov 26 '15 at 09:34
  • 4
    @IhorKaharlichenko If a variable is assigned on declaration with `local`, sub-shell exit status is "masked"/overriden by the exit status of the `local` built-in command. Declaring a local variable before assignment is also more portable (some shells don't support initialization with `local`). – vilpan Dec 05 '15 at 19:02
  • more info: https://github.com/koalaman/shellcheck/wiki/SC2155 – premek.v Apr 03 '23 at 15:27
7

From the bash manual, Shell Builtin Commands section:

local:
    [...]The return status is zero unless local is used outside a function, an invalid name is supplied, or name is a readonly variable. 

Hope this helps =)

  • Does this mean that variable definition doesn't have return status when is used without `local`? Why? Do you know some workarounds - as I don't want produce superfluous global variables. – Mephi_stofel Oct 11 '12 at 14:31
  • I'm not aware of any workarounds that don't involve the creation of a global variable or the use of files, sorry =(. I would do `local TMPFILE=$(mktemp); check-spec-file > $TMPFILE; local error=$?; local _SPEC_FILE=$(cat $TMPFILE); rm $TMPFILE` – Janito Vaqueiro Ferreira Filho Oct 11 '12 at 15:47
  • 3
    @Mephi_stofel - to work around it, you can split the declaration and initialization, e.g. first this: `local my_var` and than that: `my_var=$(my_function)`. the initialization will safely refer to the local variable, without polluting the global scope. – Eliran Malka Jan 20 '17 at 14:01
  • 1
    Thank you, @EliranMalka! I had the same problem today and this is exactly how I solved it. – JasonSmith Mar 04 '18 at 21:32
  • Amazing to find this question after you debug for one hour and only then you know how to search on Google/SO. ;) – Gabriel Petrovay Apr 12 '18 at 14:12
0

As I use bash subshell parenthesis to group many echo commands I hit this strange problem. In my case all I needed was to pass one value back to the calling shell so I just used the exit command

RET=0
echo RET: $RET
(echo hello
echo there
RET=123
echo RET: $RET
exit $RET)
RET=$?
echo RET: $RET

gives the following output

RET: 0
hello
there
RET: 123
RET: 123

without the exit command you will get this which is confusing:

RET: 0
hello
there
RET: 123
RET: 0