bash:如何根据模式从数组中删除元素

时间:2010-08-26 19:07:05

标签: arrays bash list

假设我有一个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"

6 个答案:

答案 0 :(得分:10)

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

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

将其与数组的开头和结尾进行对比:

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

答案 1 :(得分:9)

如果考虑包含空格的元素的可能性(更不用说“怪异”字符),过滤数组会很棘手。到目前为止给出的特定答案(指的是${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和最终重新分配旁边完成更多逻辑。
  7. 现在谈到这个状况。如果您可以使用[[ ]]表达式,这是一种简单的方法。 (参见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不够好。

答案 2 :(得分:7)

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

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

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

Hulk为数组提供的答案仅适用于数组。如果它似乎在扁平字符串上工作,那只是因为在测试中数组没有先取消。

e.g。

$ 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*/}

$

答案 3 :(得分:6)

你可以这样做:

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

# 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

答案 4 :(得分:1)

我定义并使用了以下功能:

# 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[@]}`)

答案 5 :(得分:1)

这是使用grep的一种方式:

()

这里的问题是int[] numbers () [] { return null; } 导致setBodyStyle扩展,并用换行符分隔了各个项目,因此可以通过grep通过管道进行传输。

尤其是,这将处理数组项内部嵌入的空间。