【问题标题】:Bash: Split two strings directly into associative arrayBash:将两个字符串直接拆分为关联数组
【发布时间】:2020-04-22 17:09:52
【问题描述】:

我有两个相同数量的子字符串除以分隔符。

我需要从子字符串创建键值对。

简短的例子: 输入:

firstString='00011010:00011101:00100001'
secondString='H:K:O'
delimiter=':'

想要的结果:

${translateMap['00011010']} -> 'H'
${translateMap['00011101']} -> 'K'
${translateMap['00100001']} -> 'O'

所以,我写道:

IFS="$delimiter" read -ra fromArray <<< "$firstString"
IFS="$delimiter" read -ra toArray <<< "$secondString"
declare -A translateMap

curIndex=0
for from in "${fromArray[@]}"; do
    translateMap["$from"]="${toArray[curIndex]}"
    ((curIndex++))
done

有没有什么方法可以直接从 2 个字符串创建关联数组,而不需要不需要的数组和循环?比如:

IFS="$delimiter" read -rA translateMap["$(read -ra <<< "$firstString")"] <<< "$secondString"

有可能吗?

【问题讨论】:

  • 恐怕不可能在没有循环的情况下分配所有内容(除了使用其他工具创建 bash 命令然后使用eval/declare 之外)。我检查了bash manual 中关键字associative 的所有匹配项,但找不到任何有用的信息。

标签: bash split associative-array


【解决方案1】:

不完全是你所问的答案,但至少它更短:

key='00011010:00011101:00100001'
value='H:K:O'
ifs=':'

IFS="$ifs" read -ra keys <<< "$key"
IFS="$ifs" read -ra values <<< "$value"
declare -A kv

for ((i=0; i<${#keys[*]}; i++)); do
    kv[${keys[i]}]=${values[i]}
done

附带说明,您可以通过以下方式一步初始化关联数组:

declare -A kv=([key1]=value1 [key2]=value2 [keyn]=valuen)

但我不知道如何在你的情况下使用它。

【讨论】:

    【解决方案2】:

    如果你的字符串中的值不使用空格,我会建议这种方法

    firstString='00011010:00011101:00100001'
    secondString='H:K:O'
    delimiter=':'
    
    declare -A translateMap
    
    firstArray=( ${firstString//$delimiter/' '} )
    secondArray=( ${secondString//$delimiter/' '} )
    
    for i in ${!firstArray[@]}; {
        translateMap[firstArray[$i]}]=${secondArray[$i]}
    }
    

    【讨论】:

      【解决方案3】:

      这可能不是您所期望的,但这有效:

      key_string="A:B:C:D"
      val_string="1:2:3:4"
      declare -A map
      while [ -n "$key_string" ] && [ -n "$val_string" ]; do
          IFS=: read -r key key_string <<<"$key_string"
          IFS=: read -r val val_string <<<"$val_string"
          map[$key]="$val"
      done
      
      for key in "${!map[@]}"; do echo "$key => ${map[$key]}"; done
      

      它使用read 函数中的递归来重新分配字符串值。 这种方法的缺点是它破坏了原始字符串。如果两个字符串的长度都不是零,while 循环会不断检查。

      在纯 bash 中,您可以使用任何命令来生成关联数组。见How do I populate a bash associative array with command output?

      这通常看起来像:

      declare -A map="( $( magic_command ) )"
      

      magic_command 在哪里生成类似的输出

      [key1]=val1
      [key2]=val2
      [key3]=val3
      

      在这种情况下,我们使用命令:

      paste -d "" <(echo "[${key_string//:/]=$'\n'[}]=") \
                  <(echo "${val_string//:/$'\n'}")
      

      我们在哪里使用bash substitution to replace the delimiter with a newline。但是,任何其他magic_command 都可以。完成:

      key_string="A:B:C:D"
      val_string="1:2:3:4"
      declare -A map="( $(paste -d "" <(echo "[${key_string//:/]=$'\n'[}]=") \
                                      <(echo "${val_string//:/$'\n'}"))      )"
      for key in "${!map[@]}"; do echo "$key => ${map[$key]}"; done
      

      两个示例都生成以下输出

      D => 4
      C => 3
      B => 2
      A => 1
      

      【讨论】:

      • 这是个好主意。但它不起作用,或者我做错了什么。你能解释一下它是如何工作的吗? “哈希”是如何声明的?可以这样用${hash[$currentKey]}吗?
      • 我将变量hash 重命名为map。它只是您的关联数组的名称。所以你用declare -A map声明它
      • 命令版本有效,但我收到数组分配错误。至少,你做了最接近我被问到的事情。我会和declare -A map="( $(paste -d "" &lt;(echo -e "[${string1//:/]=\\n[}]=") \ &lt;(echo -e "${string2//:/\\n}")) )"一起玩,谢谢!
      【解决方案4】:

      @accdias 通过declare -A 命令分配值的答案的一个(有点令人费解的)变体,但每个步骤都需要一些解释......

      首先,我们需要为每个项目将 2 个变量分成单独的行:

      $ echo "${firstString}" | tr "${delimiter}" '\n'
      00011010
      00011101
      00100001
      
      $ echo "${secondString}" | tr "${delimiter}" '\n'
      H
      K
      O
      

      这样做的好处是我们现在可以将这两组键/值对作为单独的文件处理。

      注意:对于本次讨论的其余部分,我将用 ':' 替换 "${delimiter}" 以减少一点(但不多)的复杂性。

      接下来我们使用paste 命令将我们的两个“文件”合并为一个文件;我们还将指定 ']' 作为键/值映射之间的分隔符:

      $ paste -d ']' <(echo "${firstString}" | tr ':' '\n') <(echo "${secondString}" | tr ':' '\n')
      00011010]H
      00011101]K
      00100001]O
      

      我们现在将通过几个 sed 模式运行这些结果来构建我们的数组分配:

      $ paste -d ']' <(echo "${firstString}" | tr ':' '\n') <(echo "${secondString}" | tr ':' '\n') | sed 's/^/[/g;s/]/]=/g'
      [00011010]=H
      [00011101]=K
      [00100001]=O
      

      我们现在想做的是在 typeset -A 命令中使用此输出,但不幸的是,我们需要构建整个命令,然后是 eval 它:

      $ evalstring="typeset -A kv=( "$(paste -d ']' <(echo "${firstString}" | tr ':' '\n') <(echo "${secondString}" | tr ':' '\n') | sed 's/^/[/g;s/]/]=/g')" )"
      
      $ echo "$evalstring"
      typeset -A kv=( [00011010]=H
      [00011101]=K
      [00100001]=O )
      

      如果我们想删除回车并放在一行中,我们在sed 命令的输出中附加另一个tr

      $ evalstring="typeset -A kv=( "$(paste -d ']' <(echo "${firstString}" | tr ':' '\n') <(echo "${secondString}" | tr ':' '\n') | sed 's/^/[/g;s/]/]=/g' | tr '\n' ' ')" )"
      
      $ cat "${evalstring}"
      typeset -A kv=( [00011010]=H [00011101]=K [00100001]=O )
      

      此时我们可以eval我们自动生成的typeset -A命令:

      $ eval "${evalstring}"
      

      现在循环遍历显示键/值对的数组:

      $ for i in ${!kv[@]}; do echo "kv[${i}] = ${kv[${i}]}"; done
      kv[00011010] = H
      kv[00100001] = O
      kv[00011101] = K
      

      嘿,我确实说过这有点令人费解! :-)

      【讨论】:

      • 它 eval 完美运行!我们能做到吗?不是( [00011010]=H, [00100001]=O, [00011101]=K ) 而是( [00011010]="H", [00100001]="O", [00011101]="K" ) 更好的是( ["00011010"]="H", ["00100001"]="O", ["00011101"]="K" )
      • sed "s/^/[\"/g; s/]/\"]/g; s/]/]=\"/g;" 基本完成了。但不知道如何正确粘贴最后一个\"
      • @kvantour 抱歉,没有复制您的答案;你已经编辑了几次你的答案,直到你想出与我相似的答案;我花了一段时间来撰写/发布我的信息......这似乎发生在你(重新)(重新)编辑你的答案的时候
      • 请注意eval is evil 并可能导致意外结果。
      • echo -e也有一些问题
      猜你喜欢
      • 2017-06-28
      • 1970-01-01
      • 2011-11-26
      • 2018-10-07
      • 1970-01-01
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多