5

I have an array that is not indexed from 0:

arr=([2]=aaa bbb ccc ddd)

I need to get the first index of the array. There are a lot of things I have tried and that work:

for index in "${!arr[@]}"; do
    first_index=$index
    break
done
first_index=$(awk '{ print $1 }' <<< "${!arr[@]}")
first_index=$(cut -d' ' -f1 <<< "${!arr[@]}")
first_index=${!arr[@]}
first_index=${first_index%% *}
ind=("${!arr[@]}")
first_index=${ind[@]:0:1}

What I really wanted to work:

${!arr[@]:0:1}

Given that this syntax works so well with arrays ${arr[@]:0:1} and is very clean:

  1. Is there a similar, cleaner way to do this for indexes as well without external tools, temporary arrays, temporary variables, loops etc? My attempts seem to overcomplicate such an easy task
  2. What is actually happening in ${!arr[@]:0:1}?
4
  • The loop is the approach I'd use, btw. It's verbose, but far more efficient than the attempts that use external commands. If you must use the first_index=${!arr[@]} approach, make it [*] instead so you're asking for a string explicitly, rather than asking for an array and coercing it into a string. In either case, you'll want to make sure the value of IFS is known. Commented Dec 24, 2017 at 19:23
  • 2
    ...there's no need for index, either: for first_index in "${!arr[@]}"; do break; done Commented Dec 24, 2017 at 19:29
  • (Frankly, though -- a good answer to the "what's happening on the preferred syntax?" chunk of the question would require tracing through some very hairy sections of subst.c; the people who are best equipped to do it live at [email protected]... though a question asking about syntax should go through [email protected] first). Commented Dec 24, 2017 at 19:40
  • 4
    arr=([2]=aaa bbb ccc ddd); index=(${!arr[@]}); echo ${index[0]}? Commented Dec 24, 2017 at 20:00

1 Answer 1

3

My attempts seem to overcomplicate such an easy task

The task might look easy, but might not be a common use case and therefore have no syntactic sugar to make it easy, especially if you can use other built-in features to accomplish it.

You have plenty of alternatives, here's one more:

put_first_index_in () {
    printf -v "${1?}" "%s" "${2?}"
}

put_first_index_in first_index "${!arr[@]}"

What is actually happening in ${!arr[@]:0:1}?

Indirect variable expansion:

If the first character of parameter is an exclamation point (!), and parameter is not a nameref, it introduces a level of variable indirection. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion.

With ! as the first character, three possible things are supported: ${!parameter} (the indirect expansion given above), ${!prefix*}, ${!prefix@} (expand to variable names matching the prefix), and ${!name[*]}, ${!name[@]} (expand to indexes of the name array).

The docs suggest that only ${!parameter} supports further substitution, since it is only mentioned for this and not for the others. So bash tries to do the following:

  1. Expand arr[@]
  2. Apply indirect expansion on the string obtained from (1)
  3. Get the 0:1 substring from the string obtained from indirect expansion

Since is not a valid character in identifiers, we get that error:

$ foo=SHELL
$ echo ${!foo}
/bin/zsh
$ echo ${!foo:0:1}
/
$ foo="SHELL "
$ echo ${!foo}
bash: SHELL : bad substitution
$ arr=([2]=SHELL bbb ccc ddd)
$ echo ${!arr[@]:0:1}
bash: SHELL bbb ccc ddd: bad substitution

Thus, this will only work with arrays of a single element, say:

$ arr=([2]=SHELL)

And as expected:

$ echo ${!arr[@]:0:1}
/
Sign up to request clarification or add additional context in comments.

Comments

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.