奇怪的 sed 行为

Strange sed behaviour

我有这个 POSIX 兼容的 shell 脚本。它采用分隔字符串 w.r.t。 | 并在子字符串前加上 - 如果它们的长度是单个字符:

#!/bin/sh
printf '%s\n' "k|k|jill|hill|k" | sed 's/\([|]\|^\)\([[:alnum:]]\)\([|]\|$\)/-/g'

这输出:

-k|k|jill|hill|-k

注意它没有考虑夹在两个定界符之间的 k(即 |k|)。

更奇怪的是,如果我将原始片段中的特殊字符更改为其他任何字符,它确实会在前面添加 -(请注意更改:^something$different), 但显然不是第一个和最后一个 k:

#!/bin/sh
printf '%s\n' "k|k|jill|hill|k" | sed 's/\([|]\|something\)\([[:alnum:]]\)\([|]\|different\)/-/g'

输出:

k|-k|jill|hill|k

起初我以为是因为 $^ 位置字符不是可选的。但是,对于第一个示例中的第一个标志中的 $ 和最后一个标志中的 ^,它们显然是可选的。

我很好奇,为什么这不起作用,我可以用类似的 sed 表达式做我想做的事吗?

引擎无法在中间匹配 k,因为它之前有一个成功的匹配,它的字符 (k|) 在它之前就被消耗掉了,它不能陷入匹配另一个 |。说如果你的输入字符串是:

kk|k|jill|hill|k

您会看到期望的输出。对于解决方法,我建议您设置 -r 选项以使 ERE 语法能够使用单词边界标记:

printf '%s\n' "k|k|jill|hill|k" | sed -r 's/\b([[:alnum:]])(\||$)/-/g'

或更一般地说:

printf '%s\n' "k|k|jill|hill|k" | sed -r 's/\b[[:alnum:]]\b/-[=12=]/g'

请注意,如果将 sed 脚本从全局搜索和替换更改为循环,则可以获得所需的输出:

printf '%s\n' "k|k|jill|hill|k" | sed 's/\([|]\|^\)\([[:alnum:]]\)\([|]\|$\)/-/g'
-k|k|jill|hill|-k

对比

printf '%s\n' "k|k|jill|hill|k" | sed '
    :a
    s/\([|]\|^\)\([[:alnum:]]\)\([|]\|$\)/-/
    ta
'
-k|-k|jill|hill|-k

参考:https://www.gnu.org/software/sed/manual/html_node/Programming-Commands.html