将字符串转换为关联数组的最佳方法是什么

将字符串转换为关联数组的最佳方法是什么

为了练习,我在 bash 脚本中编写了以下几行代码,将 HTTP 帖子输入转换为关联数组。在学习的过程中,我想知道是否有不同的、也许更优雅的方法来做到这一点。我的意思是,我知道有不同的方法。我想知道存在哪些方法,以及每种方法的优缺点是什么。

注意:我在这里使用 post 作为输入。然而,练习是从多个名称/值对字符串构建一个数组。输入可以来自任何文件或其他输入源。

注 2:我处理的字符串可能如下所示:name=myName&age=myAge。因此有 2 个分隔符。一个分隔名称/值对 ( &),另一个分隔值与其名称 ( =)。

#!/bin/bash
read post;
declare -A myArr;
IFS_bck=$IFS;
IFS="=";
while read name value; do
  myArr[$name]=$value;
done < <(sed -rn -e 's/&/\n/g p' <<<"$post");
IFS=$IFS_bck;

PS 我并不想挑起宗教战争。我只是想知道你会怎么做,以及为什么你会选择你的提议而不是我的。

答案1

我会利用它bash本身来做到这一点:

#!/bin/bash
read -e -p 'Enter the string: ' post
declare -A myArr

while IFS='&' read name age; do
    myArr["${name##*=}"]="${age##*=}"
done <<<"$post"

printf 'NAME: %s, AGE: %s\n' "${!myArr[@]}" "${myArr[@]}"

以下是我得到的输出:

Enter the string: name=Foo Bar&age=40
NAME: Foo Bar, AGE: 40
  • &使用IFS环境变量输出输入字符串

  • 使用参数扩展模式解析名称和年龄值${var##*=}

  • 您可以使用获取所有键${!myArr[@]}并使用获取所有值${!myArr[@]}

实际上,我认为您不会只创建一个元素的关联数组。如果您有多个元素,请将最后一行替换printf为一个简单的for构造来循环遍历键:

for key in "${!myArr[@]}"; do
    printf 'NAME: %s, AGE: %s\n' "$key" "${myArr["$key"]}"
done

答案2

您所拥有的看起来还不错,但是 Bash 和 Python 都不能sed很好地处理多个定界符的拆分;我个人会使用 AWK 并简化脚本(使用 AWK 进行更改IFS变得多余;请注意,Bash 中的语句末尾不需要分号):

#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print $1,$2}' RS='&|\n')

AWK 命令:

  • 讀數$post
  • 根据符号 / 换行符序列分割记录(根据换行符分割是一种技巧,可以防止循环while在最后的空记录上失败);
  • 按照等号序列拆分字段;
  • 打印由默认分隔符(空格)分隔的字段。
% cat script.sh 
#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print $1,$2}' RS='&|\n')
printf '%s %s\n' "${myArr[name]}" "${myArr[age]}"
% bash script.sh 
name=myName&age=myAge
myName myAge
% 

答案3

还有另一种方法,只需使用IFS

#!/bin/bash
declare -A myArr
IFS='&=' read -a post
for ((i = 0; i < ${#post[@]}; i += 2))
do
    myArr[${post[i]}]=${post[i + 1]}
done
for key in "${!myArr[@]}"
do
    printf "%s: %s\n" "$key" "${myArr[$key]}"
done

read将传入的行拆分成单词全部中的字符IFS,因此您可以使用 both&=inIFS来拆分两者。鉴于 POST 输入始终具有键的值,因此这将有效。

但是,此方法无法检查&和之间是否存在严格的交替=。因此,例如age&myAge=name=myName将被解析为age=myAgename=myName


关于IFS

您已备份IFS并恢复它。但您只需要IFSread因此IFS仅应用READ

IFS='...' read ... # or
while IFS='...' read ...; ...

恢复起来IFS很棘手,因为未设置IFS和空IFS对 shell 的影响不同,但当你获取其自身的值时是相同的IFS。即:

IFS=
IFS_BAK="$IFS"

将给出相同的值IFS_BAK

unset IFS
IFS_BAK="$IFS"

空字符串IFS就是一个空字符串。但是,取消设置IFS 使 shell 的行为就像IFS使用了默认值(空格、制表符、换行符):

$ foo='a  b  c'
$ printf "|%s|\n" $foo
|a|
|b|
|c|
$ IFS=; printf "|%s|\n" $foo
|a  b  c|
$ unset IFS; printf "|%s|\n" $foo
|a|
|b|
|c|

因此,如果您发现自己的 IFS 未设置,然后尝试旧的备份和恢复 IFS 技巧,您可能会得到令人惊讶的结果。最好将更改限制为IFS仅针对需要它的命令。

答案4

您曾说过:

因此有 2 个分隔符。一个分隔名称/值对 (&),另一个分隔值与其名称 (=)。

&好吧,我们可以使用as将名称/值对拆分IFS为变量,并使用后缀/前缀删除来释放实际的名称和年龄值。

$> cat post-parse.sh                                                           
#!/bin/bash
IFS='&' read PAIR1 PAIR2
# if necessary use these as well
# name_key=${PAIR1%%=*}
# age_key=${PAIR2%%=*}
name_val=${PAIR1##*=}
age_val=${PAIR2##*=}
echo $name_val $age_val
$> ./post-parse.sh
name=Serg&age=25
Serg 25
$> 

您还说过:

然而,练习是从多个名称/值对字符串构建一个数组。输入可以来自任何文件或其他输入源。

如果我们想要存储多个键值对,我们可以逐行读取输入(因此不需要使用sed摆脱\n那里),并应用我上面展示的相同概念:

#!/bin/bash
declare -A myArray
while read input_line ; # read input line by line
do
echo $input_line
  IFS='&' read PAIR1 PAIR2 <<< $input_line # split in two with &
  # name_key=${PAIR1%%=*}
  # age_key=${PAIR2%%=*}
  name_val=${PAIR1##*=}
  age_val=${PAIR2##*=}
  myArray[$name_val]=$age_val
done
# print out the array
for key in "${!myArray[@]}" 
do
   echo ${myArray[$key]} is $key
done

下面的示例运行使用此文档,但它可以是任何东西,甚至是管道。重点是,读取命令并不关心它从哪里获得输入。

$> ./post-parse.sh << EOF                                                      
> name=John&age=25                                                             
> name=Jane&age=35                                                             
> EOF
name=John&age=25
name=Jane&age=35
25 is John
35 is Jane

相关内容