4

I am trying to unset some variables and export others using command substitution. This works:

$(echo "
    export TEST_A=1
    export TEST_B=2
")

And this too:

$(echo "
    unset TEST_A
    unset TEST_B
")

However, this fails with Bash 4.3.30(1)-release on both OSX and Debian Jessie:

$(echo "
    unset TEST_A
    export TEST_B=1
")
# bash: unset: `TEST_B=1': not a valid identifier

Is this an expected behavior, and how can one unset and export multiple variables using command substitution?

To provide some context: here is the related issue in boot2docker.

2
  • It seems to work if you turn the unset and export lines around; do the unset last.
    – wurtel
    Jan 28, 2015 at 14:53
  • No, this does not work either: when I make unset command last, it has no effect. Try this: $(printf "export A=1\nexport B=2") && echo "A: $A, B: $B", then $(printf "export C=3\nunset A") && echo "A: $A, B: $B, C: $C". You'll see that A is still set.
    – skozin
    Jan 28, 2015 at 14:58

1 Answer 1

7

In:

$(echo "
  export TEST_A=1
  export TEST_B=2
")

being $(...), just one expansion is parsed as a simple command.

So that $(...) expansion undergoes the split+glob operator. The output of echo is trimmed of its trailing newline characters and split into several words on IFS characters (newline, space and tab by default):

  • export
  • TEST_A=1
  • export
  • TEST_B=2

And each word undergoes globbing. Here they expand to themselves since they don't contain wildcard characters. So after globbing, we still have the same list of four strings.

The command to run is derived from the first one. Here export and all those strings are passed as arguments to that command. So, here, it's as if you had written:

export TEST_A=1 export TEST_B=2

So you're exporting the TEST_A variable with value 1, the export variable without changing its value and the TEST_B variable with value 2.

In the last one, you're running:

unset TEST_A export TEST_B=1

If you want the output of echo to be treated as shell code to interpret, that's where you want to use eval and not use the split+glob operator (quote your command substitution):

eval "$(echo "
  unset TEST_A
  export TEST_B=1
")"

That one is also a simple command. Two strings: eval and <newline><spaces>unset TEST_A<newline><spaces>export TEST_B=1.

eval evaluates the content of its arguments as shell code. So it runs those two shell command lines: unset TEST_A and export TEST_B=1 (again, two simple commands).

7
  • The weird thing is that unset TEST_A;\nexport TEST_B=1 (i.e. just add a semicolon at the end of the unset line in the third example in the original question gives the error 'TEST_A;' not a valid identifier, which is not quite what you'd expect even if the whole thing is interpreted as one line; keywords here are simple command, the whole thing is interpreted as one command and not per se as one input line.
    – wurtel
    Jan 28, 2015 at 15:08
  • @wurtel, would you expect echo $(cmd) to run rm -rf ~ if cmd did output foo;rm -rf? Jan 28, 2015 at 15:20
  • Stéphane, thanks for the answer! So it seems that command substitution is not the right way to evaluate output of boot2docker shellinit, which prints several lines with unset and export commands to stdout, and the proper way is to use something like process substitution: . <(boot2docker shellinit). Am I correct?
    – skozin
    Jan 28, 2015 at 15:27
  • @sam.kozin, use eval "$(boot2docker shellinit)" if that's outputting shell code. IIRC, . <(...) used not to work in bash, but should be OK with bash4. Jan 28, 2015 at 15:30
  • @stéphane, yes, that thing outputs shell code (which consists of a bunch of unset and export commands), and eval "$(boot2docker shellinit)" works. Thank you! :)
    – skozin
    Jan 28, 2015 at 15:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .