【问题标题】:bash: how to delete elements from an array based on a patternbash:如何根据模式从数组中删除元素
【发布时间】:2011-04-04 11:06:26
【问题描述】:

假设我有一个 bash 数组(例如,所有参数的数组)并且想要删除与某个模式匹配的所有参数,或者将所有剩余元素复制到一个新数组中。或者,反过来,保持元素与模式匹配。

举例说明:

x=(preffoo bar foo prefbaz baz prefbar)

我想删除以pref 开头的所有内容以获取

y=(bar foo baz)

(顺序不相关)

如果我想为一个由空格分隔的单词列表做同样的事情怎么办?

x="preffoo bar foo prefbaz baz prefbar"

并再次删除以pref 开头的所有内容以获取

y="bar foo baz"

【问题讨论】:

    标签: arrays bash list


    【解决方案1】:

    如果考虑到元素可能包含空格(更不用说“更奇怪”的字符),过滤数组会很棘手。特别是到目前为止给出的答案(指各种形式的${x[@]//pref*/})对于此类数组将失败。

    我已经对这个问题进行了一些调查并找到了解决方案,但它不是一个很好的单线。但至少是这样。

    为了举例说明,我们假设ARR 命名我们要过滤的数组。我们将从核心表达式开始:

    for index in "${!ARR[@]}" ; do [[ …condition… ]] && unset -v 'ARR[$index]' ; done
    ARR=("${ARR[@]}")
    

    已经有几个元素值得一提:

    1. "${!ARR[@]}" 计算数组的索引(而不是元素)。
    2. "${!ARR[@]}" 表格是必须的。您不得跳过引号或将@ 更改为*。否则表达式将在键包含空格的关联数组上中断(例如)。
    3. do 之后的部分可以是任何你想要的。这个想法只是你必须为你不想在数组中包含的元素执行unset。
    4. It is advised or even needed 使用 -v 和 unset 引号,否则可能会发生不好的事情。
    5. 如果do 之后的部分如上所示,您可以使用&& 或|| 过滤掉通过或不通过条件的元素。
    6. 第二行,ARR 的重新分配,只需要用于非关联数组,并且将与关联数组中断。 (我没有很快提出一个通用表达式来处理这两种情况,而我不需要……)。对于普通数组,如果您想拥有连续的索引,则需要它。因为数组元素上的unset 不会修改(下降一个)更高索引的元素 - 它只会在索引中留下一个洞。现在,如果您只迭代数组(或将其作为一个整体扩展),这没有问题。但对于其他情况,您需要重新分配索引。另请注意,如果您在索引中有任何漏洞,那么它也会被删除。因此,如果您需要保留现有漏洞,则必须在 unset 和最终重新分配之外完成更多逻辑。

    现在谈到条件。 [[ ]] 表达式是一个简单的方法,如果你可以使用它的话。 (参见here。)特别是它支持使用Extended Regular Expressions 进行正则表达式匹配。 (请参阅here。)如果您希望数组元素不仅可以包含空格,还可以包含新行,请注意使用grep 或任何其他基于行的工具。 (虽然我认为一个非常讨厌的文件名可能有一个换行符......)


    提到问题本身,[[ ]] 表达式必须是:

    [[ ${ARR[$index]} =~ ^pref ]]
    

    (如上&& unset)


    让我们最后看看它如何处理那些困难的情况。首先我们构造数组:

    declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces")'
    ARR+=($'pref\nwith\nnew line')
    ARR+=($'\npref with new line before')
    

    我们可以通过运行declare -p ARR 并得到:

    declare -a ARR='([0]="preffoo" [1]="bar" [2]="foo" [3]="prefbaz" [4]="baz" [5]="prefbar" [6]="pref with spaces" [7]="pref
    with
    new line" [8]="
    pref with new line before")'
    

    现在我们运行过滤器表达式:

    for index in "${!ARR[@]}" ; do [[ ${ARR[$index]} =~ ^pref ]] && unset -v 'ARR[$index]' ; done
    

    另一个测试 (declare -p ARR) 给出了预期:

    declare -a ARR='([1]="bar" [2]="foo" [4]="baz" [8]="
    pref with new line before")'
    

    注意所有以pref 开头的元素是如何被删除但索引没有改变的。另请注意,${ARRAY[8]} 仍然存在,因为它以新行而不是 pref 开头。

    现在进行最后的重新分配:

    ARR=("${ARR[@]}")
    

    并检查 (declare -p ARR):

    declare -a ARR='([0]="bar" [1]="foo" [2]="baz" [3]="
    pref with new line before")'
    

    这正是预期的结果。


    结束语。如果可以将其更改为灵活的单线,那就太好了。但我认为没有办法让它更短更简单,因为它现在没有定义函数或类似的东西。

    至于函数,最好让它接受数组、返回数组并易于配置测试以排除或保留。但是我现在对 Bash 还不够好。

    【讨论】:

    • 谢谢!这很好用......而且很简单
    • 第一个代码 sn-p 包含unset -v 'ARR[$index]'。单引号不会阻止$index 的替换吗?
    • @RolandWeber,我认为这在我写这篇文章的时候确实有效。但就目前而言,它必须被检查。我不够熟练,只能通过实验来判断。
    • @RolandWeber:不。它适用于单引号。或不带引号。试试这个:a=(0 1 "and two"); for i in ${!a[*]}; do echo "$i=${a[$i]}"; ((i=1)) && unset -v 'a[$i]'; done; echo; declare -p a; a=("${a[@]}"); declare -p a
    【解决方案2】:

    这里有一个使用 grep 的方法:

    (IFS=$'\n' && echo "${MY_ARR[*]}") | grep '[^.]*.pattern/[^.]*.txt'
    

    这里的重点是 IFS=$'\n' 导致 "${MY_ARR[*]}" 扩展并用换行符分隔项目,因此可以通过 grep 进行管道传输。

    特别是,这将处理嵌入在数组项中的空格。

    【讨论】:

      【解决方案3】:

      你可以这样做:

      删除所有出现的子字符串。

      # Not specifing a replacement defaults to 'delete' ...
      echo ${x[@]//pref*/}      # one two three four ve ve
      #               ^^          # Applied to all elements of the array.
      

      编辑:

      对于空白,它是一样的

      x="preffoo bar foo prefbaz baz prefbar"
      echo ${x[@]//pref*/}
      

      输出:

      bar foo baz

      【讨论】:

      • 用空格分隔的一串单词有什么相似之处吗?
      • 似乎不太好用,在pref第一次出现后删除所有内容
      • 你在使用 bash 吗?我在那里尝试了同样的结果并得到了一个空输出
      • 为什么对 bash 有特定的要求?你不能使用 /bin/sh
      • 它并不能很好地处理数组。结果的echo 看起来很好,好吧 - 但关键是(或可能)有一个数组作为结果。例如,如果初始元素包含空格,则很难从中获取数组。有例如declare -a ARR=('element1' 'with space' 'with two spaces' 'element4'),然后做VAR=(${ARR[@]//element*/})。你将在VAR 中得到的不是两个元素的数组(with space 和 with two spaces),而是五个元素的数组(with、space、with、two、spaces )。
      【解决方案4】:

      我定义并使用了以下函数:

      # Removes elements from an array based on a given regex pattern.
      # Usage: filter_arr pattern array
      # Usage: filter_arr pattern element1 element2 ...
      filter_arr() {  
          arr=($@)
          arr=(${arr[@]:1})
          dirs=($(for i in ${arr[@]}
              do echo $i
          done | grep -v $1))
          echo ${dirs[@]}
      }
      

      示例用法:

      $ arr=(chicken egg hen omelette)
      $ filter_arr "n$" ${arr[@]}
      

      输出:

      鸡蛋煎蛋

      函数的输出是一个字符串。要将其转换回数组:

      $ arr2=(`filter_arr "n$" ${arr[@]}`)
      

      【讨论】:

      • 如果数组元素包含空格,这不会保存它们,而是拆分创建新的元素数组。您可以通过使用declare -a arr=('element1' 'with space' 'with two spaces' 'element4') 并过滤element 来查看它。结果将包含每个单词作为单独的元素,而不是仅包含 with space 和 with two spaces。
      【解决方案5】:

      另一种剥离扁平字符串的方法是将其转换为数组,然后使用数组方法:

      x="preffoo bar foo prefbaz baz prefbar"
      x=($x)
      x=${x[@]//pref*}
      

      将此与以数组开头和结尾进行对比:

      x=(preffoo bar foo prefbaz baz prefbar)
      x=(${x[@]//pref*})
      

      【讨论】:

      • 我真的很喜欢这个东西,因为它确实减少了我之前用于此类操作的代码量。
      • 它并不能很好地处理数组。例如,如果初始元素包含空格,则很难从中获取数组。例如有declare -a ARR=('element1' 'with space' 'with two spaces' 'element4'),然后做VAR=(${ARR[@]//element*/})。你将在VAR 中得到的不是两个元素的数组(with space 和 with two spaces),而是五个元素的数组(with、space、with、two、spaces )。
      【解决方案6】:

      要剥离扁平字符串(Hulk 已经给出了数组的答案),您可以打开extglob shell 选项并运行以下扩展

      $ shopt -s extglob
      $ unset x
      $ x="preffoo bar foo prefbaz baz prefbar"
      $ echo ${x//pref*([^ ])?( )}
      bar foo baz
      

      *(pattern-list) 和?(pattern-list) 表单需要extglob 选项。这允许您使用正则表达式(尽管与大多数正则表达式的形式不同),而不仅仅是路径名扩展 (*?[)。

      Hulk 给出的关于数组的答案只适用于数组。如果它似乎适用于扁平字符串,那只是因为在测试数组时没有先取消设置。

      例如

      $ x=(preffoo bar foo prefbaz baz prefbar)
      $ echo ${x[@]//pref*/}
      bar foo baz
      $ x="preffoo bar foo prefbaz baz prefbar"
      $ echo ${x[@]//pref*/}
      bar foo baz
      $ unset x
      $ x="preffoo bar foo prefbaz baz prefbar"
      $ echo ${x[@]//pref*/}
      
      $
      

      【讨论】:

      • +1 感谢您消除 Hulk 帖子中的困惑并指出另一条路径。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-27
      • 1970-01-01
      • 2021-07-20
      相关资源
      最近更新 更多