即使最后一次替换不成功,sed 条件分支“t”也会保持分支

即使最后一次替换不成功,sed 条件分支“t”也会保持分支

我有以下 sed 表达式:

echo 'abcabcabc' | sed -n ':-A s/a/x/1; s/a/&/2;t-A; p'

据说应该用除最后一个之外的所有出现'a'替换'x'。所以,预期的输出是这样的:

xbcxbcabc

但实际输出是这样的:

xbcxbcxbc

将全部替换'a''x'

我知道已经有类似的问题了,比如 替换每行中除最后一个字符之外的所有字符

但我在这里尝试使用 sed 条件分支的不同方法。

让我用我自己的理解来分解我的 sed 表达式

首先是 sed 表达式:

echo 'abcabcabc' | sed -n ':-A s/a/x/1; s/a/&/2;t-A; p'

sed 将其拉abcabcabc入模式空间

然后设置一个标签:-A

然后s/a/x/1;将第一次出现的 替换'a''x'。现在模式空间包含这个xbcabcabc

s/a/&/2;检查模式空间是否包含两个'a',它确实包含两个 ,因此它'a'用它们自己替换这两个&。所以模式空间仍然包含这个xbcabcabc

t-A由于最近的替换成功,跳回标签-A

从标签开始,-A它再次执行此操作s/a/x/1;,将模式空间的内容从 this 变为xbcabcabcthisxbcxbcabc

s/a/&/2它检查是否还有两个'a'。这次模式空间包含 thisxbcxbcabc而它没有两个,'a'因此替换不成功。

t-A因为最近的替换是不成功,它不应该跳回标签-A,而是继续p打印模式空间中的任何内容,然后xbcxbcabc退出。但相反,即使替换是不成功它再次跳回标签-A并将剩余的替换'a''x'。所以结果是这样的xbcxbcxbc

如果我l在表达式之间插入:

 echo 'abcabcabc' | sed -n ':-A s/a/x/1;  l; s/a/&/2;t-A; p'

输出:

xbcabcabc$
xbcxbcabc$
xbcxbcxbc$
xbcxbcxbc$
xbcxbcxbc

即使模式空间包含这个,我们也可以看到它再次分支xbcxbcabc

那么,我在这里缺少什么?

答案1

请注意,它将用其自身s/a/&/2替换第二个a。它不会取代两个a。同样,始终与(它将第一个替换为 )s/a/x/1相同。这与问题无关,但仍然是一个误解,在其他情况下可能会反过来咬你一口。s/a/x/ax

根据 GNU手册,t如果自读取最后一个输入行以来已成功替换,则该命令会分支,除非t此后触发了另一个命令:sed

t label
如果自从读取最后一个输入行以及最后一个or命令s///以来 a 已成功替换,则分支到;如果省略,则分支到脚本末尾。tTlabellabel

同一命令的 POSIX 规范同意这一点:

[2addr]t [label]
测试。分支到:带有label自最近读取输入行或执行 a 以来是否已进行任何替换的命令动词t。如果label未指定,则分支到脚本末尾。

因此,总结一下:如果任何s命令对于单行输入成功,那么自最近的t命令以来,该t命令将始终分支到给定的标签。

您的数据首先转换为xbcabcabc,然后转换为xbcxbcabc。当得到这个结果时,s迭代的初始命令成功地将第一个命令替换ax,因此t命令分支,给出xbcxbcxbc

解决此问题的一种方法是插入额外的t命令和虚拟标签:

echo abcabcabc |
sed -e :A -e 's/a/x/'  -e tB \
    -e :B -e 's/a/&/2' -e tA

执行tB第一个命令的“重置成功标志” s

答案2

为什么不保持简单明了并使用 awk 来代替呢?例如,使用 GNU awk 将第三个参数设置为match()

$ echo 'abcabcabc' |
    awk '{match($0,/(.*)(a.*)/,t); gsub(/a/,"x",t[1]); print t[1] t[2]}'
xbcxbcabc

或使用任何 awk:

$ echo 'abcabcabc' |
    awk '{match($0,/.*a/); t=substr($0,1,RLENGTH-1); gsub(/a/,"x",t); print t substr($0,RLENGTH)}'
xbcxbcabc

每当您发现自己考虑使用 s、g 和 p(带 -n)以外的 sed 结构时,请注意,几乎可以肯定,使用 awk 可以有一个更清晰、更简单、更高效、更健壮和/或更可移植的解决方案。

答案3

您可以反转文本,将 2 替换为 end,然后再次反转。

$ echo 'abcabcabc' | rev | sed 's/a/x/2g' | rev
xbcxbcabc

在这个简单的情况下不需要标签和循环,除非您正在做一些 sed 递归功能的练习。

相关内容