Search code examples
bashfunctioneval

Environment Variable Not Visible when Evaluating a Function with a Sub-shell


This is a minimal example of a more complex script I'm writing. The key thing I want to do is have $FOO visible to that sub shell in the second line of function foo.

However, as shown, it's not visible for the first evaluation but oddly visible for the second evaluation.

❯ function foo() {
  echo "export FOO=BAR"
  echo "export CAR=\$(echo \$FOO)"
}

~
❯ eval $(foo)

~
❯ echo $CAR

~
❯ eval $(foo)

~
❯ echo $CAR
BAR

Solution

  • eval $(foo) is expanded to:

    eval 'export FOO=BAR export CAR=$(echo $FOO)'
    

    The export has the effect of causing both assignments to be processed 'at the same time' and since FOO is not known prior to this line of code the second assignment becomes CAR=$(echo '')=''. It's only on the 2nd eval $(foo) that FOO has a value so CAR=BAR.

    To get around this side effect and have CAR=BAR (for the first eval $(foo)) you can either remove the export or have the function print a ; between the two exports (explicitly separate the exports as two separate commands), eg:

    # remove 'export'
    
    foo() {
      echo "FOO=BAR"
      echo "CAR=\$(echo \$FOO)"
    }
    
    # explicitly separate into 2 commands via a ';'
    
    foo() {
      echo "export FOO=BAR ;"                # notice the ';' appended on the end
      echo "export CAR=\$(echo \$FOO)"
    }
    

    For the 2nd foo() definition the eval $(foo) is expanded to:

    eval 'export FOO=BAR ; export CAR=$(echo $FOO)'
    

    With two separate commands FOO is assigned a value before we parse/execute CAR=$(echo $FOO).


    We can see this behavior of export with a simpified example (sans the issues mentioned in Gordon Davisson's comment):

    $ export A=3 B="$A"
    $ typeset -p A B
    declare -x A="3"
    declare -x B=""              # A does not have a value so B=""
    
    $ export A=3 B="$A"
    $ typeset -p A B
    declare -x A="3"
    declare -x B="3"             # A now has a value (from 1st run) so B=3