Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a variable to index ${array[*]} in bash?

First of all sorry because my english may be not good. I want to use a variable to index an element in an array or use the same variable to index all the elements. For example:

...
var1="1"
var2="*"
array=(one two three for five)

for elem in ${array[$var1]}
do
  echo $elem
done

When I use var1 to index in ${array[$var1]} it works correctly, but if I use var2 doesn't work correctly, I get this error:

./ed.sh line XXX *: syntax error: operand expected (error token is "*")

I'm pretty sure that the error is related with the * wildcard expansion, but I didn't find an answer that help me to solve this problem. So, how can I do it?

like image 490
Eduardo Pascual Aseff Avatar asked Jan 31 '18 19:01

Eduardo Pascual Aseff


People also ask

How do I index an array in bash?

Similar to other programming languages, Bash array elements can be accessed using index number starts from 0 then 1,2,3…n. This will work with the associative array which index numbers are numeric. To print all elements of an Array using @ or * instead of the specific index number.

How do you assign a variable to an index array?

Assigning values to associative array variable elements can be done by using the assignment statement in which the array is named, the index value is specified and the corresponding element value is assigned.

How do I echo an array in bash?

How to Echo a Bash Array? To echo an array, use the format echo ${Array[0]}. Array is your array name, and 0 is the index or the key if you are echoing an associative array. You can also use @ or * symbols instead of an index to print the entire array.


2 Answers

* and @ are not considered regular elements in the array. They are not listed when iterating keys, and are not considered when expanding indirectly through index variables.

The bash source code has a function chk_atstar that checks whether [@] or [*] is being used, and you can see that it's done literally and not through any expansion:

else if (valid_array_reference (name, 0))
  {
    temp1 = mbschr (name, '[');
    if (temp1 && temp1[1] == '@' && temp1[2] == ']')
      {

If you really want to do this, you can go through variable indirection:

arr=(one two three)
index='*'
var="arr[$index]"
echo "${!var}"

though you may be better off not trying to treat these special array access modes as array elements.

like image 155
that other guy Avatar answered Oct 02 '22 03:10

that other guy


I don't recommend this, but for completeness you can get this to work by cheating with the expansion order using eval:

eval items=\${array[$var2]}
for elem in $items
do
    echo $elem
done

There are issues with this. eval is generally pronounced "evil" because there can be security implications in running code from a variable. There is usually a better way to do the job than using eval. In this case you should give some thought to the design.

There is also an issue if an element contains embedded whitespace. Add:

array+=('at the end')

After the array declaration and you'll see what I mean.

EDIT: After some deliberation, here is a way to do it without eval, and it supports embedded spaces or tabs (but not embedded newlines). Pretty it is not:

display_it() {
    if [[ $1 = '*' ]]; then
        oldIFS="$IFS"
        IFS=$'\n'
        echo "${array[*]}"
        IFS="$oldIFS"
    else
        echo "${array[$1]}"
    fi
}

var1="1"
var2="*"
array=(one two three for five)
array+=('at the end')

while read -r elem
do
  echo $elem
done < <(display_it "$var2")

Displays:

one
two
three
for
five
at the end

At the end of the loop you will see process substitution where I call the function display_it. Each item read is separated by a newline, hence the swapping of the Internal Field Separator (IFS) in the function.

like image 26
cdarke Avatar answered Oct 02 '22 04:10

cdarke