【问题标题】:Remove element from bash array by content (stored in variable) without leaving a blank slot [duplicate]按内容从bash数组中删除元素(存储在变量中)而不留下空白槽[重复]
【发布时间】:2014-06-21 04:30:40
【问题描述】:

我在 bash 脚本中有一个数组 list,还有一个变量 var。我知道$var 出现在${list[@]} 中,但没有简单的方法来确定它的索引。我想从list 中删除它。

This answer 实现了非常接近我需要的东西,除了 list$var 曾经所在的位置保留了一个空元素。注意,例如:

$ list=(one two three)
$ var="two"
$ list=( "${list[@]/$var}" )
$ echo ${list[@]}
one three
$ echo ${#list[@]}
3

如果我在第三行使用delete=( "$var" ) 并用$delete 替换$var,也会发生同样的事情。此外,做list=( "${list[@]/$var/}" ) 也没有区别。 (我会注意到,在尝试对该答案的评论时,我设法使用list=( "${list[@]/%$var}" ) 仅匹配整个单词,省略了#。)

我还看到this 的回答提出了一个很好的技巧来跟踪索引并使用unset,但这在我的情况下是不可行的。最后,同样的问题也出现了here,除了 OP 对结果感到满意,并且可能没有遇到稍后在我的脚本中为我创建的空元素问题,当我遍历 list 时。我试图通过使用以下扩展来否定这个问题,但没有任何明显的效果:

for item in "${list[@]}"; do
  if [ -n ${item:+'x'} ];then
    ...
  fi
done

当我做[ ${#item} > 0 ] 时也是如此,而且我的想法已经不多了。有什么建议吗?

编辑:

我不明白为什么会发生这种情况,但@l0b0 的评论让我注意到了一些事情。使用上面的序言,我得到:

$ for item in "${list[@]}"; do echo "Here!"; done
Here!
Here!
Here!

但是:

$ for item in ${list[@]}; do echo "Here!"; done
Here!
Here!

我不确定是否可以在脚本中省略引号,因为那里的项目要复杂得多(文件名和路径,都包含空格和奇数字符)。

【问题讨论】:

  • 关于您的编辑,我无法复制它。两次运行我都得到了三个 Here! 实例。
  • @JS웃 很奇怪。它在我的机器上完全可以重现(运行可靠)。
  • 您的编辑在第二种情况下获得两个实例的原因是列表 ("one" "" "three") 被 shell 扩展为 for item in one three 而不是 for item in "one" "" "three"
  • @JoshuaGoldberg 你有没有注意到这个问题是我提供的第一个链接,然后解释了为什么它还不够? (也就是说,空元素。)
  • @JonathanY.,我很抱歉,我确实错过了......虽然它可能更多地限制了那里的大部分答案,而不是问题。 (我在那里发布了一个有点老套的答案,并没有碰巧留下空白位置。)对于寻找标记 dup 的答案或只是保持链接的人们是否会更有帮助,我持观望态度。

标签: arrays bash unset


【解决方案1】:

如果要删除数组元素并移动索引,可以使用l0b0或JS웃的answer。

但是,如果您不想移动索引,可以使用以下 script-let: (对关联数组特别有用)

$ list=(one two three)
$ delete_me=two
$ for i in ${!list[@]};do
    if [ "${list[$i]}" == "$delete_me" ]; then
        unset list[$i]
    fi 
done

$ for i in ${!list[@]};do echo "$i = ${list[$i]}"; done
0 = one
2 = three

如果要移动索引以使它们连续,请按以下方式重新构造数组:

$ list=("${list[@]}")
$ for i in ${!list[@]};do echo "$i = ${list[$i]}"; done
0 = one
1 = three

【讨论】:

    【解决方案2】:

    您可以从现有数组中删除一个元素,尽管整个过程不是很简单,并且可能看起来像一个 hack。

    #!/bin/bash
    
    list=( "one" "two" "three" "four" "five" )
    var1="two"
    var2="four"
    
    printf "%s\n" "Before:"
    for (( i=0; i<${#list[@]}; i++ )); do 
        printf "%s = %s\n" "$i" "${list[i]}"; 
    done
    
    for (( i=0; i<${#list[@]}; i++ )); do 
        if [[ ${list[i]} == $var1 || ${list[i]} == $var2 ]]; then
            list=( "${list[@]:0:$i}" "${list[@]:$((i + 1))}" )
            i=$((i - 1))
        fi
    done
    
    printf "\n%s\n" "After:"
    for (( i=0; i<${#list[@]}; i++ )); do 
        printf "%s = %s\n" "$i" "${list[i]}"; 
    done
    

    此脚本输出:

    Before:
    0 = one
    1 = two
    2 = three
    3 = four
    4 = five
    
    After:
    0 = one
    1 = three
    2 = five
    

    脚本的关键部分是:

    list=( "${list[@]:0:$i}" "${list[@]:$((i + 1))}" )
    

    在这里,我们通过指定索引和长度来重新构建您现有的数组,以从数组中完全删除元素并重新排序索引。

    【讨论】:

    • 这篇文章不正确。如果您使用var1="two" var2="three",它将失败,因为在您循环数组时索引会移动。所以它将是索引 1,删除“二”,然后转到索引 2,现在是“四”,因此跳过测试三。要更正此 i=$((i - 1)) 应在 if 语句中插入。
    【解决方案3】:

    如果您想按值删除并移动索引,我认为您必须创建一个新数组:

    list=(one two three)
    new_list=() # Not strictly necessary, but added for clarity
    var="two"
    for item in ${list[@]}
    do
        if [ "$item" != "$var" ]
        then
            new_list+=("$item")
        fi
    done
    list=("${new_list[@]}")
    unset new_list
    

    测试:

    $ echo "${list[@]}"
    one three
    $ echo "${#list[@]}"
    2
    

    【讨论】:

    • 谢谢!但是,由于我可能需要在一次运行中多次执行此过程,因此如果可能的话,我真的更愿意避免这种方法。 (并不是说我期望我正在做的事情的复杂性以任何真正的方式很重要,但是事情的原则:))
    猜你喜欢
    • 2011-08-18
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 2013-05-20
    • 2011-07-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多