循环遍历数组,打印索引和值

我想这样做:

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

然后我尝试通过它循环使用for in:

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

但这里我不知道下标值。

我知道你可以

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

但是,你不能用另一种方法吗?

270185 次浏览

你会发现数组键是"${!foo[@]}" (参考),所以:

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

这意味着索引将在$i中,而元素本身必须通过${foo[$i]}

你总是可以使用迭代参数:

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

在bash 4中,你可以使用关联数组:

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.

在bash 3中,这是有效的(在zsh中也有效):

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


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

简单的一行技巧转储数组

我用空格添加了一个值:

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

为了快速转储数组或关联数组,我使用

一行命令:

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

将呈现:

12  bar
35  baz
42  foo bar baz

解释

  • printf "%s\n" "${!foo[@]}"将打印所有由换行符分隔的
  • printf "%s\n" "${foo[@]}"将打印所有由换行符分隔的
  • paste <(cmd1) <(cmd2)将逐行合并cmd1cmd2的输出。

校准

这可以通过-d switch进行调整:

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

甚至:

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

关联数组的工作原理是一样的:

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"

使用换行或特殊字符发出

不幸的是,至少有一个条件使这个不再工作:当变量包含换行符:

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

命令paste将逐行合并,因此输出将是错误的:

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'

对于这项工作,你可以在第二个printf命令中使用%q而不是%s(并使用whpe引号):

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

将呈现完美(和可重用!):

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

man bash:

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

或者使用函数:

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
}

然后现在:

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!

关于UTF-8格式输出

UTF-8字符串长度,添加:

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

然后

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
}

演示:

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