17

I want to use arrays in my sh script.

My target is to create an array with the values a b c and print all values in the array.

I succeeded to print each array, but I failed to print all values in the array.

Following example:

Set each value in arr:

n=1
eval arr$n=a
n=2
eval arr$n=b
n=3
eval arr$n=c

Print each value from arr:

n=1
eval echo \$arr$n
a
n=2
eval echo \$arr$n
b
n=3
eval echo \$arr$n
c

Now I want to print all values in $arr and instead of a b c I get:

n="*"
eval echo \$arr$n
{*}*

The values should be a b c.

maihabunash
  • 7,131
  • check this question http://unix.stackexchange.com/questions/137566/array-in-unix-bourne-shell – Nischay Jul 03 '14 at 07:51
  • When you are limited to /bin/sh try avoiding too much scripting. How much colleagues will understand your eval-calls? You do want to go on vacation some day. – Walter A Jul 03 '14 at 13:43

3 Answers3

45

Little late, but I don't see the ideal sh answer here so I'll chime in. If you don't need subscripting, then sh effectively does support arrays. It just supports them as space separated strings. You can print the entire contents of them, "push" to them, or iterate through them just fine.

Here's a bit of sample code:

NAMES=""
NAMES="${NAMES} MYNAME"
NAMES="${NAMES} YOURNAME"
NAMES="${NAMES} THEIRNAME"

echo 'One at a time...'
for NAME in ${NAMES}; do
    echo ${NAME};
done

echo 'All together now!'
echo ${NAMES}

Which outputs:

One at a time...
MYNAME
YOURNAME
THEIRNAME
All together now!
MYNAME YOURNAME THEIRNAME

Now, I said it doesn't support sub-scripting, but with a little bit of cut magic and using the space as a proper delimiter, you can absolutely emulate that. If we add this to the bottom of our above example:

echo 'Get the second one'
echo ${NAMES} | cut -d' ' -f2

echo 'Add one more...'
NAMES="${NAMES} TOM"

echo 'Grab the third one'
echo ${NAMES} | cut -d' ' -f3

And run it, we get:

Get the second one
YOURNAME
Add one more...
Grab the third one
THEIRNAME

Which is what we'd expect!

However, a string with a space in it can cause an issue and will completely break the sub-scripting.

So, really the better statement is: Arrays are non-obvious in sh and handling arrays of strings with spaces is hard. If you don't need to do that (e.g., an array of host names you want to deploy to), then sh is still a fine tool for the job.

hjc1710
  • 691
  • Wasn't able to reproduce the cut part as each elements are thrown in a new line instead of a space when echoing the variable – Zulgrib May 24 '17 at 22:26
  • That my be due to the shell you're using. Can you share your script (shebang included) and shell version? – hjc1710 May 25 '17 at 23:03
11

sh does not support array, and your code does not create an array. It created three variable arr1, arr2, arr3.

To initialize an array element in a ksh-like shell, you must use syntax array[index]=value. To get all element in array, use ${array[*]} or ${array[@]}.

Try:

n=1
eval arr[$n]=a
n=2
eval arr[$n]=b
n=3
eval arr[$n]=c

n=1
eval echo \${arr[$n]}
n=2
eval echo \${arr[$n]}
n=3
eval echo \${arr[$n]}

n='*'

eval echo \${arr[$n]}
cuonglm
  • 153,898
0

I agree that the correct answer is to find a different approach.  Get a better shell, switch to some other scripting language (maybe perl or python), or rethink your design.  But, to get a list of the values of all the variables whose names begin with arr, you can use

set | sed -n '/^arr[^=]*=/s///p'

This will list them on separate lines.  To get them all on one line, use

echo $(set | sed -n '/^arr[^=]*=/s///p')

Not that this will sort them in lexicographical order, so arr10arr19 will appear between arr1 and arr2.  (The same thing happens if you have files named maih1, maih2, …, maih10, …, maih19.)  If you know in advance how many array elements you are going to have, you can fix this by using leading zeroes; e.g., arr01. arr02, …, will get you up to 99.

P.S. The trick with set | sed … will fail if any of the variables’ values contain newline(s).