7

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?

3
  • What exactly is the result you're looking for? Your array does not have an index that is *, because non-associative arrays only have integers as keys. Commented Jan 31, 2018 at 19:26
  • @ghoti: ${array[*]} is valid for indexed arrays, it displays all the elements of the array. ${!array[*]} displays all the indexes that have values, which can be useful because these arrays are sparse. The problem here is the expansion order, which even eval does not appear to fix. Commented Jan 31, 2018 at 19:33
  • 2
    Welcome to SO! Thanks for including complete, dependency-free code to reproduce the problem plus a complete error message! +1. Commented Jan 31, 2018 at 19:39

2 Answers 2

6

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

Sign up to request clarification or add additional context in comments.

Comments

1

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.

1 Comment

Scalar arrays already evaluate variables as code, so eval (sadly) doesn't actually introduce a new security issue in this case

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.