Bash变量替换中的正则表达式不按预期工作

5

假设有一个bash变量保存了以下字符串:

INPUT="Cookie: cf_clearance=foo; __cfduid=bar;"

为什么替换${INPUT/cf_clearance=[^;]*;/}产生的输出是Cookie: ,而不是我期望的Cookie: __cfduid=bar;
在在线正则表达式验证器中测试相同的正则表达式可以确认cf_clearance=[^;]*;只匹配cf_clearance=foo;而不匹配字符串的其余部分。
我在这里做错了什么?

2
谁告诉你bash支持使用内置字符串替换的正则表达式??? - iBug
@iBug,就我所知,它确实有效。像${MYVAR//[a-z]/X}这样更简单的模式可以工作。 - Oscar Hierro
1
那不是正则表达式。它只是一个括号表达式,甚至被 printf()scanf() 支持(如果你编写 C 程序)。 - iBug
1
顺带一提,还可以参考 https://dev59.com/anRB5IYBdhLWcg3wSVYI。 - tripleee
谢谢,现在我明白我有错误的期望。TIL! - Oscar Hierro
3个回答

11

使用实际的正则表达式匹配功能而不是与模式一起工作的参数扩展。

[[ $INPUT =~ (.*)(cf_clearance=[^;]*;)(.*) ]]
ans=${BASH_REMATCH[1]}${BASH_REMATCH[3]}

您也可以使用扩展模式,它在功能上与正则表达式等效:

shopt -s extglob
$ echo "${INPUT/cf_clearance=*([^;]);/}"

1
我认为那是正确的答案。请注意,这两个选项仅适用于Bash;它们在其他shell(例如Ubuntu的dashkshzsh等)中无法工作。问题专门针对Bash,因此这不是答案的问题,但需要注意。 - Zac B
它在Python、Perl、Haskell、Ruby或其他许多语言中也无法工作;您想要注意吗?kshzsh都有解决方案可用(可能与bash的解决方案没有太大区别),而POSIX shell则需要多次使用expr命令。 - chepner
1
我并不是要批评这个答案;我认为它很好也是正确的。我指出其他shell的不兼容性,是因为“bashisms”是初学者在编写shell时常见的困惑源。这只是需要注意的一点;问题是关于Bash的,而且你的答案再次是100%正确的。 - Zac B

4

使用sed

INPUT=$(sed 's/cf_clearance=[^;]*;//' <<< "$INPUT")

谢谢。我知道我可以使用sed甚至awk,但我想了解为什么在这种情况下bash替换不起作用。 - Oscar Hierro
2
据我所知,Bash不支持使用内置替换的正则表达式,只支持通配符。 - iBug
我一直在犹豫应该接受哪个答案。说实话,这是一个非常简单和易于理解的方法,因此也是完全有效的。 - Oscar Hierro
@oscahie 由你决定接受哪一个。随着你在Stack Overflow上的参与越来越多,你会遇到更多这样的情况,所以不要太担心。选择你喜欢的并继续前进。干杯! - iBug

1

像评论中所说的那样,Bash参数替换仅支持glob模式,而不是正则表达式。因此问题实际上在于您的期望,而不是代码本身。

如果您知道表达式可以锚定到字符串开头,您可以使用${INPUT#prefix}参数替换来获取最短可能的匹配,并在前面添加回Cookie:

echo "Cookie: ${INPUT#Cookie: cf_clearance=*;}"

如果您没有这个保证,可以通过一对参数替换来近似实现。找到在 cf_clearance 之前的部分,找到在 cf_clearance 分号后面的部分;将它们粘合在一起即可。
head=${INPUT%cf_clearance=*}
tail=${INPUT#*cf_clearance=*;}
echo "$head$tail"

如果您不害怕复杂的替换,那么临时变量并不是必要或有用的。
echo "${INPUT%cf_clearance=*}${INPUT#*cf_clearance=*;}"

这对于我这样老练的口味来说,有点过于密集了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接