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

kyn*_*nan 19 arrays bash list

假设我有一个bash数组(例如所有参数的数组),并希望删除与特定模式匹配的所有参数,或者将所有剩余元素复制到新数组.或者,反过来,保持元素匹配模式.

举例说明:

x=(preffoo bar foo prefbaz baz prefbar)
Run Code Online (Sandbox Code Playgroud)

我想删除所有内容pref以便获取

y=(bar foo baz)
Run Code Online (Sandbox Code Playgroud)

(订单不相关)

如果我想要用空格分隔的单词列表,那该怎么办?

x="preffoo bar foo prefbaz baz prefbar"
Run Code Online (Sandbox Code Playgroud)

然后再次删除所有内容pref以便获取

y="bar foo baz"
Run Code Online (Sandbox Code Playgroud)

Ada*_*ura 15

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

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

为了举例说明,我们假设arr名称是我们要过滤的数组.我们将从核心表达开始:

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

已经很少有值得一提的元素:

  1. "${!ARR[@]}" 计算数组的索引(而不是元素).
  2. 表格"${!ARR[@]}"是必须的.您不得跳过引号或更改@*.否则表达式将在关键数组中中断,其中键包含空格(例如).
  3. 之后的部分do可以是你想要的任何东西.这个想法只是你必须unset按照你不希望在数组中拥有的元素所示.
  4. 建议甚至需要使用-v和报价,unset否则可能会发生坏事.
  5. 如果之后的部分do如上所述,您可以使用&&||过滤掉通过或失败条件的元素.
  6. 第二行,即重新分配ARR,只需要非关联数组,并且会与关联数组分开.(我没有迅速推出一个通用的表达式来处理这两个,而我不需要一个......).对于普通数组,如果要连续索引,则需要它.因为unset在数组元素上不会修改(逐个删除)更高索引的元素 - 它只会在索引中形成一个漏洞.现在,如果你只迭代数组(或整个扩展它),这没有问题.但是对于其他情况,您需要重新分配索引.另请注意,如果索引中有任何漏洞,也会将其删除.因此,如果您需要保留现有漏洞,则必须在unset最终重新分配之外完成更多逻辑.

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


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

[[ ${ARR[$index]} =~ ^pref ]]
Run Code Online (Sandbox Code Playgroud)

(&& 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')
Run Code Online (Sandbox Code Playgroud)

通过运行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")'
Run Code Online (Sandbox Code Playgroud)

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

for index in "${!ARR[@]}" ; do [[ ${ARR[$index]} =~ ^pref ]] && unset -v 'ARR[$index]' ; done
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

ARR=("${ARR[@]}")
Run Code Online (Sandbox Code Playgroud)

并检查(declare -p ARR):

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

这正是预期的结果.


截止日期.如果可以将其改成灵活的单行程,那就太好了.但是我认为没有办法让它变得更短更简单,因为它现在没有定义功能或类似功能.

至于函数,让它接受数组,返回数组并且易于配置测试以排除或保留它也会很好.但是我现在对Bash不够好.


Pau*_*ce. 10

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

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

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

x=(preffoo bar foo prefbaz baz prefbar)
x=(${x[@]//pref*})
Run Code Online (Sandbox Code Playgroud)

  • 它对于数组来说并不是很好地工作。例如,如果初始元素包含空格,则很难从中获取数组。例如 `declare -a ARR=('element1' 'with space' 'with two space' 'element4')` 然后执行 `VAR=(${ARR[@]//element*/})`。你在“VAR”中得到的不是一个由两个元素组成的数组(“with space”和“with two space”),而是一个由五个元素组成的数组(“with”、“space”、“with”、“two”、 `空格`)。 (2认同)

Hul*_*ulk 6

你可以这样做:

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

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

编辑:

对于白色空间,它有点相同

x="preffoo bar foo prefbaz baz prefbar"
echo ${x[@]//pref*/}
Run Code Online (Sandbox Code Playgroud)

输出:

酒吧foo baz

  • 它实际上不适用于数组。结果的'echo'看起来不错,但是-重点是(或可能)具有一个数组。例如,如果初始元素包含空格,则很难从中获得数组。例如,使用`declare -a ARR =('element1''with space''with两个空格''element4')',然后执行VAR =($ {ARR [@] // element * /})。您将在VAR中得到的不是两个元素的数组(带有空格的空间和带有两个空格的带有空间的数组),而是五个元素的数组(带有空格的空间,带有空格的空间,带有两个空格的空间,空间)。 (3认同)

cam*_*amh 6

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

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

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

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

$
Run Code Online (Sandbox Code Playgroud)