6

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?

JuanD
  • 191
  • 1
    What's the (practical) difference between that and SRVR_ARRAY=($HOST_NAMES)? – Michael Homer Mar 23 '17 at 05:06
  • Until I parse through the $HOST_NAMES variable it is one long string. Thats why I had to feed it to AWK – JuanD Mar 23 '17 at 05:12
  • Try it and see. – Michael Homer Mar 23 '17 at 05:17
  • No dice. Even tried using double quotes instead of single quotes when setting the HOST_NAMES environment variable. In both cases, it resulted in the first array index location having the entire string server1 server2 server3 – JuanD Mar 23 '17 at 05:21
  • 1
    @JuanD Variable references that are not in double-quotes get split into "words" based on whitespace -- spaces, tabs, and linefeeds. Thus, in 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 [). – Gordon Davisson Mar 23 '17 at 05:22
  • @JuanD Have you changed $IFS to something nonstandard? – Gordon Davisson Mar 23 '17 at 05:23
  • What shell is your script written for? – Michael Homer Mar 23 '17 at 05:23
  • Havent modified $IFS and writing for BASH – JuanD Mar 23 '17 at 05:24
  • Well, Im embarrassed @MichaelHomer was right. Used SRVR_ARRAY=($HOST_NAMES) and it worked. – JuanD Mar 23 '17 at 05:30
  • I don't understand why you need SRVR_ARRAY at all. Why not do for h in $HOST_NAMES; do ping $h; done? – Philippos Mar 23 '17 at 06:49
  • @Philippos you're also right that I could avoid using the array altogether.

    -Thanks to everyone for their input.

    – JuanD Mar 23 '17 at 11:47

2 Answers2

5

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.

ilkkachu
  • 138,973
1

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).