【问题标题】:Looping over arrays, printing both index and value循环遍历数组,打印索引和值
【发布时间】:2011-10-07 02:18:00
【问题描述】:

我想做这样的事情:

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

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

【问题讨论】:

    标签: bash


    【解决方案1】:

    您会找到带有"${!foo[@]}" (reference) 的数组键,所以:

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

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

    【讨论】:

    • 重要提示,虽然是可迭代的,但以空格分隔的单词列表不是数组。像 (a b c) 那样包装它以将其转换为数组。
    • [@] 和双引号的使用意味着它不是“以空格分隔的单词列表”。即使单个键包含空格,您也会得到实际数组键的列表。
    • @glennjackman 你能解释一下吗The use of [@] and double quotes means it's not a "space separated list of words"
    • "${foo[@]}" 采用(数组)变量foo 并将其扩展为数组,保持其元素的身份,即不将它们拆分为空白。如果foo=(x 'y z'),则f "${foo[@]}" 使用两个参数f 调用x'y z'。像 "${!foo[@]}""${#foo[@]}" 这样的元数据查询类似地作用于 foo 作为一个数组。
    • 正确答案,但 reference 是不可理解的。 This answer 只是解释道:“"${!foo[@]}" 是数组中设置的所有索引的列表”。
    【解决方案2】:

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

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

    【讨论】:

    • ((ITER++)) 在现代 bash 中
    • 为什么是后增量?您只希望值递增,因此 ((++ITER)) 更直接地说明了您想要完成的操作 ....
    • 不,不是“总是”。哈希可以有“洞”,这意味着并非所有数字都是索引。在您的示例中,${ITER} 并不总是 ${I} 的索引。
    【解决方案3】:
    INDEX=0
    for i in $list; do 
        echo ${INDEX}_$i
        let INDEX=${INDEX}+1
    done
    

    【讨论】:

    • 我喜欢简洁的答案和策略。对于任何想知道这是 bash 的人来说,策略是您只需自己跟踪数组索引。
    【解决方案4】:
    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
    

    【讨论】:

    • 这是错误的!内循环是无用的,可能会导致错误的结果!
    【解决方案5】:

    在 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
    

    【讨论】:

    • 尝试 bash 3 版本(在 zsh 中),它在键和值上都回显了 value..
    • 我验证了第二个版本在 Zsh 5.8 中对我来说可以正常工作,但是,如果您使用的是 Zsh,第一个版本更可取。对于 Zsh,将 for key in "${!foo[@]}" 替换为 for key in "${(k)foo[@]}"
    【解决方案6】:

    转储数组的简单一行技巧

    我添加了一个带空格的值:

    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 &lt;(cmd1) &lt;(cmd2) 将逐行合并cmd1cmd2 的输出。

    调整

    这可以通过-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'
    

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

    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 string length,添加:

    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
    

    【讨论】:

    • 我对@9​​87654350@ newline 的投票是我的问题!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-30
    • 1970-01-01
    • 1970-01-01
    • 2017-07-09
    • 2015-07-10
    • 1970-01-01
    相关资源
    最近更新 更多