I looked around for an answer to the question "Can I use an array as an environment variable and then call it from a Bash shell script" and came to the conclusion that it was not "officially" supported as (outlined here, and here).
However I really had a need to do this and came up with a "work-around" and wanted to get your opinions.
Setup: I created a variable in my .profile
file that looks something like:
HOST_NAMES='server1 server2 server3'
Then at the begining of my shell script I wrote:
SRVR_ARRAY=($(echo $HOST_NAMES | awk '{for (i = 1; i <=NF; i++) print $i " "}'))
Later on in the script when it was time to do some work, I called:
for h in "${SRVR_ARRAY[@]}"; do
ping $h
done
And it worked! Now I'm sure this is Bash shell scripting jerry-riggin at its finest but wanted to see if anyone could see any risks in this usage?
2 Answers 2
The problems you might face are the usual ones: splitting and globbing.
You can't have strings with whitespace, since they'll be split to separate array items, and anything resembling a glob character (*?[]
) will be expanded to matching filenames. Probably not a problem with host names though, but in general, it is.
This is a common issue, at repeatedly discussed, see e.g. Expansion of a shell variable and effect of glob and split on it and Security implications of forgetting to quote a variable in bash/POSIX shells
To work around those, you'd need to a) set IFS
to something other than whitespace and separate your strings with that, and b) disable globbing with set -f
.
This would use |
as a separator and split the string. You could use any character not needed in the values, maybe even some control character like 001円
(Use $'001円'
in Bash to create it).
#!/bin/bash
string='foo|bar'
IFS='|'; set -f
array=($string) # split it
IFS='|'
string="${array[*]}" # merge it back together
Also, like they say in the comments, if your variable only has hostnames and you are fine with splitting on whitespace, you don't need the awk to split it. The shell will do it when assigning to array or starting the loop:
SRVR_ARRAY=( $HOST_NAMES )
for x in $HOST_NAMES ; do ...
(Again, you'll get trouble if HOST_NAMES
contains glob characters.)
Actually, that's what is going to happen even with your example: awk
prints something, the shell catches it, splits it on words, since the $()
was not inside double-quotes, and then saves the words separately in the array.
You can store the output of declare -p
in the environment variable instead:
array=(foo 'bar baz'); array[12]=sparse
export ARRAY_definition="$(declare -p array)"
Then, in the executed bash
script:
eval "$ARRAY_definition"
Note that it's important that the eval
be executed in the same locale as the declare
one (and preferably same version of bash
)
If the eval
is not run in the global scope, the array will be declared local.
With zsh
, you can use quoting to avoid having to use that dangerous eval
:
export ARRAY_definition=${(j: :)${(q)array}}
import with:
array=(${(Q)${(z)ARRAY_definition}})
Alternatively, you could use a shell like rc
, es
or fish
that supports exporting arrays natively (using their own encoding).
SRVR_ARRAY=($HOST_NAMES)
?server1 server2 server3
SRVR_ARRAY=($HOST_NAMES)
, the list of host names will be split, and each name stored as a separate entry in the array. As Michael said, try it. BTW, neither is entirely safe if any of the host names might contain whitespace (technically, that means any of the characters in$IFS
, which actually might be anything) and/or shell wildcards (*
,?
, or[
).