As of bash 4.2, you can just use a negative index ${myarray[-1]}
to get the last element. You can do the same thing for the second-last, and so on; in Bash:
If the subscript used to reference an element of an indexed array
evaluates to a number less than zero, it is interpreted as relative to
one greater than the maximum index of the array, so negative indices
count back from the end of the array, and an index of -1 refers to the
last element.
The same also works for assignment. When it says "expression" it really means an expression; you can write in any arithmetic expression there to compute the index, including one that computes using the length of the array ${#myarray[@]}
explicitly like ${myarray[${#myarray[@]} - 1]}
for earlier versions.
you should handle that stuff in the index evals. and you can indirect through your indirection variable's indices if you make it an array.
a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for i in 0 1 2 3 4 5
do c=
printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
printf "\n\tindex is $i and count is $c\n\n"
done
<abc1>
index is 0 and count is 5
<def2>
index is 1 and count is 5
<ghi3>
index is 2 and count is 5
<jkl4>
index is 3 and count is 5
<mno5>
index is 4 and count is 5
<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>
index is 5 and count is 5
Because bash
's indices are 0-based, the total count of array objects will always work out to one more than than the highest set index, and so:
c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"
this index is unset 5
...the parameter expands out to the default word if any is provided.
If one is not provided:
c=
${!r}
echo "$c"
5
...there's no harm done.
In the loop I track an $i
ndex variable and check if it is at least as large as $c
ount. When it is lesser I expand the $r
eference var to a[i]
because it is a valid index, but when it is equal or greater I expand the $r
ef to the entire $a
rray.
Here it is in a function:
ref_arr(){
local index=-1 count=
local ref=( "$1[ count= \${#$1[@]} ]"
"$1[ index ]" "$1[ @ ]"
) && printf "input array '%s' has '%d' members.\n" \
"$1" "${!ref-${count:?invalid array name: "'$1'"}}"
while [ "$((index+=1))" -lt "$count" ]
do printf "$1[$index] == '%s'\n" "${!ref[1]}"
done
}
some_array=(some "dumb
stuff" 12345\'67890 "" \
'$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'
input array 'some_array' has '5' members.
some_array[0] == 'some'
some_array[1] == 'dumb
stuff'
some_array[2] == '12345'67890'
some_array[3] == ''
some_array[4] == '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'
Best Answer
In zsh, personal preference. In other shells
$array
may only expand to the first element, thus${#array}
would output the length of the first element.So, if you want to be little more portable between shells specifying the
[@]
would work.In zsh,
$array
expands in the same way$array[*]
would, which differs depending on if they appear within quotes or not. Should they appear within double quotes"$array"
would expand and be delimited by the first character ofIFS
which by default is spaceChanging
IFS
is rarely needed which prompted my original "Personal preferences" response. But just to clarify there is a few differences between the two when used without the#
flag, they are is just very subtle.I prefer
$array[@]
also since it's behavior doesn't change depending on whether or not it appears within quotes. That and internal whitespace that an element may have is preserved.