【问题标题】:Strange sed behaviour奇怪的 sed 行为
【发布时间】:2018-02-06 19:14:20
【问题描述】:

我有这个 POSIX 兼容的 shell 脚本。它需要一个带分隔符的字符串 w.r.t. | 并将 - 添加到子字符串(如果它们的长度是单个字符):

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

这个输出:

-k|k|jill|hill|-k

请注意,它不考虑夹在两个分隔符之间的 k(即 |k|)。

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

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

输出:

k|-k|jill|hill|k

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

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

【问题讨论】:

  • 它不考虑 k 被夹在中间,因为它消耗了上一场比赛中的第一个字符 |。所以没有什么可以再匹配了。
  • @revo 您能否在答案中对此进行扩展?也许展示导致它失败的步骤?我不完全理解你的评论

标签: regex shell sed posix


【解决方案1】:

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

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

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

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

【讨论】:

  • sed 在我看来总是一个可怕的怪物。阅读您提到的页面,但仍然不明白这种循环在这种情况下是如何工作的。你介意解释一下吗? +1
  • 绝对可怕的人。感谢您的出色回答!
  • @revo 我认为它的工作原理类似于 goto; a 是 goto 的标签,而 ta 正在调用函数 t,它会返回到带标签的 goto。所以你回到a,循环。不确定它如何不重新匹配第一个模式。 Sed 是一个黑盒子
  • t 命令仅在之前的s 命令确实进行了替换时才会分支。这个例子基本上是一个while循环
【解决方案2】:

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

kk|k|jill|hill|k

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

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

或更笼统地说:

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

【讨论】:

  • 绝妙的答案!关于如何以符合 POSIX 的方式进行操作的任何建议? ERE 是我在这种情况下无法使用的东西
  • 请查看@glenn 的完美答案。
  • 非常感谢您的回答!从中学到了很多关于 sed 的知识:)
猜你喜欢
  • 2015-03-28
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 2014-12-27
  • 2012-04-21
  • 2023-03-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多