为什么使用负模式匹配和参数扩展过滤 bash 数组会产生意外结果?

为什么使用负模式匹配和参数扩展过滤 bash 数组会产生意外结果?

我正在做一个小型的纯 bash 脚本来掷骰子;因此我必须操作数组。我想做一些类似于filter其他语言中的操作:提取数组中的一些内容,并将它们放入另一个中。

我希望将这些值保留为 bash 数组,因为使用数组进行其他一些操作(即跨索引切片)要容易得多。

我可以通过for... do... done循环来完成此操作,但我很好奇为什么我的模式匹配不能按预期工作。

shopt -s extglob;
dicerolls=(a b lol kek yolo swag ); 
c=(${dicerolls[@]/!(kek)/}); 
declare -p c;
# Expected: declare -a c=([0]="kek")
# Got: declare -a c=([0]="k")

# One can also see it with this example:
dicerolls=(20 15 7 8 9 0 14 5 6 200 144); c=(${dicerolls[@]/!(14)/}); declare -p c;
# Expected: declare -a c=([0]="14")
# Got: declare -a c=([0]="4")

# Oddly, this works for single-character values
dicerolls=(20 15 7 8 9 0 14 5 6 200 144); c=(${dicerolls[@]/!(8)/}); declare -p c;
# Got: declare -a c=([0]="8")

编辑来自 @ikkachu 的更简单的示例

var="abcd"
echo "${var/!(abcd)/}"
# Result: d
# Expected: abcd

它似乎正确匹配(并丢弃)模式中没有的所有内容,但是当遇到模式中包含的内容时,似乎只获得${dicerolls[@]/%!(14)/}匹配的最后一个(或第一个,如果使用)字符。我在手册中找不到任何与某种长度限制相关的内容,也找不到任何保证匹配不被截断的内容。

我觉得这很奇怪,并且没有找到解释,更不用说对此行为的“修复”了。

那么问题是:有没有办法通过数组参数扩展内部的模式匹配来获得上面的预期结果?

答案1

# Expected: declare -a c=([0]="kek")
# Got: declare -a c=([0]="k")

我认为我们可以将其简化为这样的:

$ var="abcd"
$ echo "${var/!(abcd)/}"
d

和我思考var="abbbcd"; echo "${var/a+(b)/}"发生的情况与prints类似cd。 shell 尝试a+(b)从字符串的开头开始查找与模式匹配的匹配项,并查找最长的匹配项。abbbcd或者abbbc不匹配,但abbb匹配,并且该部分被删除。

(或者更确切地说,它可能从 开始a,看到它与子模式匹配a,并继续看到ababbabbb匹配整个模式,但因为abbbc不匹配,abbb所以是最长的匹配。)

类似地,对于模式!(abcd),整个abcd字符串不是匹配,但子字符串abc匹配。并被删除,留下d.

或者,就你的情况而言kek不是匹配!(kek),但ke确实如此,留下k.

相似地:

$ var="abcd"
$ echo "${var//!(a*)/}"
a

整个字符串abcd不匹配!(a*),并且从第一个字母开始的任何内容也不匹配a,因此匹配器继续从第二个字母开始。bcd匹配!(a*)并被删除。

这看起来不直观,但它与字符串中任何位置的匹配都计数的所有其他情况类似。例如,尝试b*e针对字符串的模式abcdefg,匹配bcde字符串中间的部分。只是对于否定模式,匹配的字符串可能会有很大差异。

无论如何,您可能会更好地使用其他一些编程语言,一种具有更好的数据结构工具的语言,例如“过滤器”函数。

另请注意,尽管它对于此处的过滤思想有点用,但c=(${dicerolls[@]/!(kek)/});如果任何数组元素包含空格或通配符,则未加引号的扩展会导致问题。适当的“过滤”功能不会有这样的问题。

相关内容