bash:从数组中删除变量?

3
#!/bin/bash

tank=(one two three)
x=two

unset tank[${x}]
echo ${tank[*]}

我想把数组中的x移除,但它总是移除数组的第一个元素。我该如何修复这个问题?
3个回答

4
你有一个索引数组,因此 [...] 中的值被视为算术表达式以生成整数索引。这样的表达式中的字符串被视为参数名称,而未定义的参数将被计算为零。由于 two 是未定义的,您的尝试被评估为
unset tank[${x}] -> unset tank[two] -> unset tank[0]

要从数组中安全地移除一个元素,你需要遍历整个数组,将非匹配项复制到一个新的数组中,然后将新的数组赋值回旧的数组名称。这可以防止分割可能包含空格的数组元素。
x=two
new_tank=()
for i in "${tank[@]}"; do
    if [[ $i != $x ]]; then
        new_tank+=("$i")
    fi
done
tank=( "${new_tank[@]}" )

更简洁地说,正如gniourf_gniourf所指出的那样:
for i in "${!tank[@]}"; do
    [[ ${tank[i]} = $x ]] && unset tank[i]
done

根据您的应用程序不同,您可能需要考虑使用关联数组。

declare -A tank
tank=([one]=1 [two]=2 [three]=3)   # Using the keys as the actually elements
x=two
unset tank[$x]
# Prove that two is really gone, with no hole left behind.
for i in "${!tank[@]}"; do
   echo "$i"
done

你也可以不使用辅助数组:for i in "${!tank[@]}"; do [[ ${tank[i]} = $x ]] && unset tank[i]; done; tank=( "${tank[@]}" ) - gniourf_gniourf
啊,好主意。我会加上的。 - chepner
请注意,[[ $i != $x ]]会将$x的内容视为全局模式,而不是字面字符串。因此,如果您尝试从数组中删除*,则*将匹配所有内容,并将删除数组中的所有元素。如果您想要仅删除精确匹配而不是模式匹配,则需要对$x进行双引号引用(即[[ $i != "$x" ]])。实际上,除非有特定原因,否则我倾向于推荐对所有变量引用进行双引号引用(即[[ "$i" != "$x" ]]);这比记住何时需要双引号更容易且更安全。 - Gordon Davisson

1

由于无法像正则表达式 ^two$ 那样精确匹配元素,我唯一看到的方法是使用test[[执行精确匹配,重新构建一个新数组,排除不需要的元素:

tank=( first two twot twoot twooot )
unset work
x=two
for i in ${tank[@]}; do [[ $i = $x ]] || work+=($i); done
tank=$work

0

编辑 user2997549 注意:尽管该解决方案在这种情况下似乎有效,但通常是错误的。以下命令对数组中的所有元素执行模式替换,实际上仅在您完全控制数组内容的情况下才起作用。请接受 chepner 的答案,它更加通用并解决了其他问题,这样我就可以删除我的答案。

要按值(而不是索引)完全删除元素,您需要一个新数组,如下所示:

newtank=( ${tank[@]/two/} )

如果你只是取消一个索引,那么这个数组中仍然会有空洞;或者你可以摆脱旧数组。
tank=( ${tank[@]/${x}/} )

2
为什么不直接使用 tank=${tank[@]/$x/} 呢? - janos
那个会起作用。谢谢! - user2997549
@janos 因为以下代码行是 echo ${tank[*]} - guido
2
这在许多情况下都不起作用:如果tank的字段中有空格。此外,这会删除tank中每个字段中第一个匹配的two。你肯定不想使用这个!! - gniourf_gniourf
如果你有这个数组:tank=( two twot twoot twooot ),你的方法并不像 OP 期望的那样有效。 - gniourf_gniourf
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接