Awk 单行仅替换第一个匹配的正则表达式出现的文本

Awk one-liner to replace text of first matching regex occurence only

我需要这个 awk 命令将文本中第一个 XML 标签中的 ss:Width="252" 替换为 ss:Width="140" 并保留其余标签一个人:

cat <<- EOF > text
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="189"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="189"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
EOF

awk '{c=++count[[=10=]]} c==1 {sub(/ss:Width=\"[0-9]{1,4}\"/,"ss:Width=\"140\"")} {print}' text > newf

cat newf

相反,它替换了三个唯一匹配项中每一个的第一个实例中的表达式(总共替换了三个,而我只想要一个。)

<ss:Column ss:AutoFitWidth="1" ss:Width="140"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="140"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="140"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="189"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
<ss:Column ss:AutoFitWidth="1" ss:Width="252"/>

为什么会这样?增量器在我的 awk 命令中表现如何?我预计它会在 /ss:Width=\".*\"/ 的第一场符合条件的比赛之后增加,但它似乎不会增加,直到找到所有 unique 比赛,然后仅忽略后续的非唯一匹配项。那正确吗?我试图强制计数器在 c == 1 块的末尾递增,如下所示:

awk '{c=++count[[=12=]]} c==1 {sub(/ss:Width=\".*\"/,"ss:Width=\"140\"");c++} {print}' text > newf

但我得到了相同的输出。我没有运气在 sed 中尝试这个任务&我宁愿在 awk 中完成它。我对理解这个 awk 语法特别感兴趣。

编辑:我通过将其中一个宽度属性更改为另一个随机数来测试这个理论。它也确实用 140 替换了那个。因此,它限制为所有匹配表达式的第一个实例,而不是第一个匹配表达式本身。

编辑:正如 Cody 所指出的,我的正则表达式是贪婪的。我将 .* 更改为 [0-9]{1,4} 但行为是相同的 - 它仍然仅替换每个唯一匹配项的第一个实例。我还将 XML 标签的其中一个宽度属性更改为第三个唯一编号,并更新了输出以说明我正在尝试修复的行为。

这是AIX/ksh。

awk 'found == 0 { found = sub(/ss:Width=\"[0-9]{1,4}\"/,"ss:Width=\"140\"")} //' text > newf

你也许可以缩短一点。

您的旧方法是保留一个由输入行索引的计数器数组。这就是它表现出您意想不到的行为的原因。

其他一些答案假设所有行都将匹配 /ss:Width/ 正则表达式 and/or 总是在行尾找到 width 属性。在您的情况下可能是正确的,但值得注意。我决定不在上面的脚本中假设这些东西。

看起来你的正则表达式是贪婪的。

sub(正则表达式, 替换 [ 目标]) sub 函数改变了 target 的值。它在这个被视为字符串的值中搜索与正则表达式 regexp 匹配的 最左边最长的子字符串。

试试这个:

awk '([=10=] ~ /ss:Width/) {if (once != 1) {sub("[0-9]+\"/>","140\"/>")}; once=1; print}' text

它查找包含 ss:Width 的第一行,然后用 140 替换结束标记前的最后一个数字。

使用自定义字段分隔符实际上非常简单:

awk -F ' ss:Width="252"' -v r=' ss:Width="140"' '!p && NF>1{p=1;  =  r} 1' text
    <ss:Column ss:AutoFitWidth="1" ss:Width="140"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="189"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="189"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="126"/>
    <ss:Column ss:AutoFitWidth="1" ss:Width="252"/>

-F ' ss:Width="252"' 将字段分隔符设置为 ss:Width="252"

!p && NF>1 为搜索文本的第一个实例放置替换值 r