296

I want to do something like this:

foo=( )
foo[0]="bar"
foo[35]="baz"
for((i=0;i<${#foo[@]};i++))
do
    echo "$i: ${foo[$i]}"
done
# Output:
# 0: bar
# 1: 

Then i tried to loop through it using for in:

foo=( )
foo[0]="bar"
foo[35]="baz"
for i in ${foo[@]}
do
    echo "?: $i"
done
# Output:
# ?: bar
# ?: naz

but here I don't know the index value.

I know you could something like

foo=( )
foo[0]="bar"
foo[35]="baz"
declare -p foo
# Output:
# declare -a foo='([0]="bar" [35]="baz")'

but, can't you do it in another way?

6 Answers 6

506

You would find the array keys with "${!foo[@]}" (reference), so:

for i in "${!foo[@]}"; do 
  printf "%s\t%s\n" "$i" "${foo[$i]}"
done

Which means that indices will be in $i while the elements themselves have to be accessed via ${foo[$i]}

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

15 Comments

Important note, while iterable, a space separated list of words is not an array. Wrap it like so (a b c) to convert it to an array.
The use of [@] and double quotes means it's not a "space separated list of words". You get the list of actual array keys, even if the individual keys contain whitespace.
@glennjackman can you explain this more The use of [@] and double quotes means it's not a "space separated list of words"
"${foo[@]}" takes the (array) variable foo and expands it as an array, maintaining the identity of its elements, i.e., not splitting them on whitespace. If foo=(x 'y z'), then f "${foo[@]}" calls f with two arguments, x and 'y z'. Metadata queries like "${!foo[@]}" and "${#foo[@]}" similarly act on foo as an array.
Right answer, but that reference is inscrutable. This answer helpfully just explains: ""${!foo[@]}" is the list of all the indexes set in the array".
|
45

you can always use iteration param:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done

3 Comments

((ITER++)) in modern bash
Why post-increment ? You only want the value incremented, hence ((++ITER)) is more directly a statement of what you want done ....
No, not "ALWAYS". A hash can have "holes", which means not all numbers are an index. In your example the ${ITER} is not always the index of ${I}.
31
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done

1 Comment

I liked the conciseness of the answer and strategy. For anyone wondering this is bash and the strat is you just keep track of the array index on your own.
19

Simple one line trick for dumping array.

I've added one value with spaces:

foo=([12]="bar" [42]="foo bar baz" [35]="baz")

For a quick dump of arrays or associative arrays I use this one line command:

paste <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")

Will render:

12  bar
35  baz
42  foo bar baz

Explanation

  • printf "%s\n" "${!foo[@]}" will print all keys separated by a newline,
  • printf "%s\n" "${foo[@]}" will print all values separated by a newline,
  • paste <(cmd1) <(cmd2) will merge output of cmd1 and cmd2 line by line.

Tuning

This could be tuned by -d switch:

paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")

Output:

12:bar
35:baz
42:foo bar baz

or even:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")

foo[12]='bar'
foo[35]='baz'
foo[42]='foo bar baz'

Associative array will work the same:

declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
paste -d = <(printf "bar[%s]\n" "${!bar[@]}") <(printf '"%s"\n' "${bar[@]}")



bar[foo bar]="Hello world!"
bar[foo]="snoopy"
bar[bar]="nice"
bar[baz]="cool"

Issue with newlines or special chars: unfortunately, there is at least one condition making this not work anymore: when variable do contains a newline:

foo[17]=$'There is one\nnewline'

Command paste will merge line-by-line, so output will become wrong:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")



foo[12]='bar'
foo[17]='There is one
foo[35]=newline'
foo[42]='baz'
='foo bar baz'

For this work, you could use %q instead of %s in second printf command (and wipe quoting):

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "%q\n" "${foo[@]}")

Will render perfect ( and reusable! ):

foo[12]=bar
foo[17]=$'There is one\nnewline'
foo[35]=baz
foo[42]=foo\ bar\ baz

From man bash:

%q     causes  printf  to output the corresponding argument in a
               format that can be reused as shell input.

Dump array in reusable format (without loop): pure , without loop and without fork, using two step, you could build a variable ready to re-define your array elsewhere.

Another way of doing this by rendering a format string for printf command:

printf -v fmt '[%d]=%%q\\n' "${!foo[@]}"
printf "$fmt" "${foo[@]}"

Output:

[12]=bar
[17]=$'There is one\nnewline'
[35]=baz
[42]=foo\ bar\ baz

Note: by using printf -v you could populate a string variable that you could use to define a new variable:

printf -v fmt '[%d]=%%q\\n' "${!foo[@]}"
printf -v asString "$fmt" "${foo[@]}"
declare -a "newVar=($asString)"
declare -p newVar 
declare -a newVar=([12]="bar" [17]=$'There is one\nnewline' [35]="baz" [42]="foo bar baz")

But for Associative arrays, you have to care about presence of percent sign in index names:

bar['100%var']=42
varIdx=("${!bar[@]}")
printf -v fmt '[%q]=%%q\\n' "${varIdx[@]//\%/%%}"
printf "$fmt" "${bar[@]}"


[100%var]=42
[foo]=snoopy
[bar]=nice
[baz]=cool
[foo\ bar]=Hello\ world\!

Then same, for creating a new associative array:

printf -v asString "$fmt" "${bar[@]}"
declare -A "newAssoc=($asString)"
declare -p newAssoc 
declare -A newAssoc=([100%var]="42" [foo]="snoopy" [bar]="nice" [baz]="cool" ["foo bar"]="Hello world!" )

By using a function:

dumpArray() {
    local -n _ary=$1
    local _idx
    local -i _idlen=0
    for _idx in "${!_ary[@]}"; do
        _idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
    done
    for _idx in "${!_ary[@]}"; do
        printf "%-*s: %s\n" "$_idlen" "$_idx" \
            "${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C  }"
    done
}

Then now:

dumpArray foo
12: bar
17: There is one
    newline
35: baz
42: foo bar baz

dumpArray bar
foo    : snoopy
bar    : nice
baz    : cool
foo bar: Hello world!

About UTF-8 format output

From UTF-8 string length, adding:

strU8DiffLen() { local chLen=${#1} LANG=C LC_ALL=C;return $((${#1}-chLen));}

or better, using printf -v _ '%s%n'

Then

dumpArray() {
    local -n _ary=$1
    local _idx
    local -i _idlen=0 _idbytes
    for _idx in "${!_ary[@]}"; do
        _idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
    done
    for _idx in "${!_ary[@]}"; do
        printf -v _ '%s%n' "$_idx" _idbytes
        printf "%-*s: %s\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
            "${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C  }"
    done
}

Demo:

foo=([12]="bar" [42]="foo bar baz" [35]="baz")
declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')

foo[17]=$'There is one\nnewline'
LANG=fr.UTF-8 printf -v bar[déjà]  $'%(%a %d %b\n%Y\n%T)T' -1

dumpArray bar
déjà   : ven 24 déc
         2021
         08:36:05
foo    : snoopy
bar    : nice
baz    : cool
foo bar: Hello world!

dumpArray foo
12: bar
17: There is one
    newline
35: baz
42: foo bar baz

More complete function

  • Align number to right, in both: keys and values
  • Support for UTF-8 in keys and values
  • Support multi-line in keys and values

Code:

dumpArray() { 
    local -n _ary=$1
    local _idx _align
    local -i _idlen=0 _idbytes _valLen
    case ${_ary@a} in 
        *a*) _idlen=(${!_ary[@]})
             _idlen=${#_idlen[${#_idlen[@]}-1]} ;;
        *A*) for _idx in "${!_ary[@]}"; do
                _idlen=" ${#_idx} >_idlen ? ${#_idx} : _idlen "
             done
             _align=- ;;
        *) return 1 ;;
    esac
    if [[ ${_ary@a} == *i* ]]; then
        read -r _valLen < <(printf '%d\n' "${_ary[@]}"|sort -rn)
        _valLen=${#_valLen}
            for _idx in "${!_ary[@]}"; do
            printf -v _ '%s%n' "$_idx" _idbytes
            printf "%${_align}*s: %*d\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
                $_valLen "${_ary["$_idx"]}"
        done
    else
            for _idx in "${!_ary[@]}"; do
            printf -v _ '%s%n' "$_idx" _idbytes
            printf "%${_align}*s: %s\n" $((_idbytes-${#_idx}+_idlen)) "$_idx" \
                "${_ary["$_idx"]//$'\n'/$'\n\e['${_idlen}C  }"
        done
    fi
}

With auto-completion:

_dumpArray() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=(
        $(compgen -W '$(
            declare |
                sed -ne "s/^\([^ ]\+\)=(.*/\1/p"
                )' -- "${cur-}"
        )
    )
}
complete -F _dumpArray dumpArray


declare -ai testIArray=([12345]=34 [12]=14141551 [42]=664446)
dumpArray testIArray
   12: 14141551
   42:   664446
12345:       34

See also

1 Comment

My up vote for printf "%q\n" "${var[@]}" newline was my problem!
7

In bash 4, you can use associative arrays:

declare -A foo
foo[0]="bar"
foo[35]="baz"

# for Zsh, change this to: for key in "${(k)foo[@]}"
for key in "${!foo[@]}"
do
    echo "key: $key, value: ${foo[$key]}"
done

# output
# $ key: 0, value bar.
# $ key: 35, value baz.

In bash 3, this works (also works in zsh):

map=( )
map+=("0:bar")
map+=("35:baz")

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done

2 Comments

trying the bash 3 version (in zsh), it echoes out the value for both key and value..
I verified the second version works fine for me in Zsh 5.8, however, the first version is preferable if you're using Zsh. Replace for key in "${!foo[@]}" with for key in "${(k)foo[@]}" for Zsh.
1
users=("kamal" "jamal" "rahim" "karim" "sadia")
index=()
t=-1

for i in ${users[@]}; do
  t=$(( t + 1 ))
  if [ $t -eq 0 ]; then
    for j in ${!users[@]}; do
      index[$j]=$j
    done
  fi
  echo "${index[$t]} is $i"
done

1 Comment

This is wrong! Inner loop is useless and could drive to wrong results!

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.