Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this Bash script not inheriting all environment variables?

Tags:

bash

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?

like image 534
Nick Chammas Avatar asked Jul 10 '18 19:07

Nick Chammas


1 Answers

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[*]).

like image 111
Nick Chammas Avatar answered Sep 27 '22 23:09

Nick Chammas