遍历数组,同时打印索引和值


184

我想做这样的事情:

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

然后我尝试使用for循环遍历它:

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")'

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

Answers:


316

您会发现带有"${!foo[@]}"reference)的数组键,因此:

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

这意味着索引将在$i元素本身必须通过以下方式访问时${foo[$i]}


6
重要说明,虽然可以迭代,但用空格分隔的单词列表不是数组。像这样包装它,(a b c)以将其转换为数组。
2016年

4
使用[@]和双引号表示它不是“用空格分隔的单词列表”。您将获得实际数组键的列表,即使单个键包含空格也是如此。
格琳·杰克曼(Glenn Jackman)'16

@glennjackman您能详细说明一下吗The use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya

1
"${foo[@]}"接受(array)变量foo并将其扩展为数组,从而保持其元素的标识,即,不要在空格上拆分它们。如果foo=(x 'y z'),则使用两个参数和进行f "${foo[@]}"调用。元数据查询就像并且类似地作用于数组。fx'y z'"${!foo[@]}""${#foo[@]}"foo
BallpointBen

正确的答案,但是这种引用是难以理解的。该答案很有帮助,仅说明:“ "${!foo[@]}"是数组中设置的所有索引的列表”。
pjhsea

17

您可以随时使用迭代参数:

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

5
((ITER++))在现代狂欢中
David Tonhofer

为什么要后期递增?您只希望值增加,因此((++ ITER))更直接地表示您想要做的事情
...。– MikeW

3
不,不是“总是”。散列可以具有“空洞”,这意味着并非所有数字都是索引。在您的示例中,$ {ITER}并不总是$ {I}的索引。
马可(Marco)

10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done

您的回答当然值得一点解释。请参考stackoverflow.com/help/how-to-answer。评论将有助于创建可搜索的内容。
J. Chomel

3
尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。也请尽量不要在代码中加入解释性注释,这会降低代码和解释的可读性!
kayess

2

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

declare -A foo
foo[0]="bar"
foo[35]="baz"
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

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
错了!内循环是无用的,可能导致错误的结果!
F. Hauri

1

转储数组的简单

我用空格添加了一个值:

foo=()
foo[12]="bar"
foo[42]="foo bar baz"
foo[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)将逐行合并cmd1和的输出cmd2

调音

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

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'

对于这项工作,您可以在第二个命令(和鞭打引号)中使用%q而不是:%sprintf

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.

我对printf "%q\n" "${var[@]}" 换行符的最高投票是我的问题!
techno
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.