7

I'm trying something very straightforward:

PEOPLE=(
  "nick"
  "bob"
)
export PEOPLE="$(IFS=, ; echo "${PEOPLE[*]}")"
echo "$PEOPLE"  # prints 'nick,bob'
./process-people.sh

For some reason, process-people.sh isn't seeing $PEOPLE. As in, if I echo "$PEOPLE" from inside process-people.sh, it prints an empty line.

From what I understand, the child process created by invoking ./process-people.sh should inherit all the parent process's environment variables, including $PEOPLE. Yet, I've tried this on both Bash 3.2.57(1)-release and 4.2.46(2)-release and it doesn't work.

What's going on here?

Nick Chammas
  • 11,843
  • 8
  • 56
  • 115

1 Answers1

10

That's a neat solution you have there for joining the elements of a Bash array into a string. Did you know that in Bash you cannot export array variables to the environment? And if a variable is not in the environment, then the child process will not see it.

Ah. But you aren't exporting an array, are you. You're converting the array into a string and then exporting that. So it should work.

But this is Bash! Where various accidents of history conspire to give you the finger.

As @PesaThe and @chepner pointed out in the comments below, you cannot actually convert a Bash array variable to a string variable. According to the Bash reference on arrays:

Referencing an array variable without a subscript is equivalent to referencing with a subscript of 0.

So when you call export PEOPLE=... where PEOPLE was previously assigned an array value, what you're actually doing is PEOPLE[0]=.... Here's a fuller example:

PEOPLE=(
  "nick"
  "bob"
)
export PEOPLE="super"
echo "$PEOPLE"  # masks the fact that PEOPLE is still an array and just prints 'super'
echo "${PEOPLE[*]}"  # prints 'super bob'

It's unfortunate that the export silently fails to export the array to the environment (it returns 0), and it's confusing that Bash equates ARRAY_VARIABLE to ARRAY_VARIABLE[0] in certain situations. We'll just have to chalk that up to a combination of history and backwards compatibility.

Here's a working solution to your problem:

PEOPLE_ARRAY=(
  "nick"
  "bob"
)
export PEOPLE="$(IFS=, ; echo "${PEOPLE_ARRAY[*]}")"
echo "$PEOPLE"  # prints 'nick,bob'
./process-people.sh

The key here is to assign the array and derived string to different variables. Since PEOPLE is a proper string variable, it will export just fine and process-people.sh will work as expected.

It's not possible to directly change a Bash array variable into a string variable. Once a variable is assigned an array value, it becomes an array variable. The only way to change it back to a string variable is to destroy it with unset and recreate it.

Bash has a couple of handy commands for inspecting variables that are useful for investigating these kinds of issues:

printenv PEOPLE  # prints 'nick,bob'
declare -p PEOPLE_ARRAY  # prints: declare -ax PEOPLE_ARRAY='([0]="nick" [1]="bob")'

printenv will only return a value for environment variables, vs. echo, which will print a result whether the variable has been properly exported or not.

declare -p will show the full value of a variable, without the gotchas related to including or leaving out array index references (e.g. ARRAY_VARIABLE[*]).

Nick Chammas
  • 11,843
  • 8
  • 56
  • 115
  • 3
    `Referencing an array variable without a subscript is equivalent to referencing with a subscript of 0.` So all that `PEOPLE=$(...)` did was assign the result of the command substitution to `PEOPLE[0]`. So there is no conversion, it's still an array that cannot be exported :) – PesaThe Jul 10 '18 at 19:20
  • @PesaThe - Not sure what you're referring to. Is there a specific line of code I wrote that doesn't work the way I said it would? – Nick Chammas Jul 10 '18 at 19:49
  • Ah, you're saying that the assignment of a string to an array variable just populates the 0-index of the array. Correct? So even if I do `export PEOPLE="some string"`, it will only overwrite the 0-index of the `PEOPLE` array? – Nick Chammas Jul 10 '18 at 19:51
  • 1
    @NickChammas Right; once the array attribute on a name is set, assigning to the name alone will always assign to the 0th element of the array. You can't unset the array attribute (short of unsetting the name itself). – chepner Jul 10 '18 at 19:58
  • PesaThe and chepner - Thank you. Even when I think I have the answer, SO goes deeper and shows me the shallowness of my knowledge. I will update my answer accordingly. – Nick Chammas Jul 10 '18 at 20:01
  • 1
    You can even see the minimal change necessary to turn a regular variable into an array. (This isn't strictly related to the question, but it's fun to watch happen, for some extremely minimal value of "fun".) After `x=3`, run `declare -p x` to see that it is a regular variable. Then run `declare -a x` followed by `declare -p x`. You'll see that the former value of `x` is now the value of the 0th element of the array `x`. – chepner Jul 10 '18 at 20:03