Looping over arrays, printing both index and value
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?
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]}
you can always use iteration param:
ITER=0
for I in ${FOO[@]}
do
echo ${I} ${ITER}
ITER=$(expr $ITER + 1)
done
INDEX=0
for i in $list; do
echo ${INDEX}_$i
let INDEX=${INDEX}+1
done
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 bash 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
Explained
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 ofcmd1
andcmd2
line by line.
Tunning
This could be tunned by -d
switch:
paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
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 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
Unfortunely, there is at least one condition making this not work anymore: when variable do contain 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 whipe 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.
Or 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));}
Then
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
strU8DiffLen "$_idx"
printf "%-*s: %s\n" $(($?+$_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
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
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
精彩评论