【问题标题】:Awk one-liner to replace text of first matching regex occurence onlyawk one-liner 仅替换第一个匹配的正则表达式出现的文本
【发布时间】:2025-12-22 00:25:11
【问题描述】:

我需要这个 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[$0]} 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[$0]} c==1 {sub(/ss:Width=\".*\"/,"ss:Width=\"140\"");c++} {print}' text > newf

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

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

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

这是 AIX/ksh。

【问题讨论】:

    标签: regex awk ksh aix


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

    你也许可以缩短一点。

    您的旧方法是保留由输入行索引的计数器数组。这就是为什么它表现出你没有预料到的行为。

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

    【讨论】:

    • 谢谢 - 这个解释是我怀疑的,但这个解决方案对我不起作用,我明白了,但还不能解决它。尝试在各个地方添加一个结束括号,但没有运气。 “错误上下文是 // {line=$0} found == 0 >>> {line={
    • 我正在尝试分解您的语法并理解它,我认为这将有很大帮助。另外,我需要它匹配 [0-9]{1,4},而不仅仅是 252(在第一个 XML 标记中可能并不总是 252。)
    • 只能在 NR == 1 时进行替换吗?我想我不太确定你想影响哪些线路。
    • 我编辑了答案以匹配宽度属性中的任何数值。我认为这可能是问题所在。
    • Shawn,你说得对,并非所有行都包含 ss:Width ,我相信这就是 NR == 1 不起作用的原因。这只是一个较大的 XML 文档的摘录,我需要在其中识别这个文本块并转换这一系列标签中的第一个。我得到了要执行的命令,但现在 - 我不知道为什么 - 它只在第一个匹配项上打印“1”而不是完整的 XML 标记。 1 等我试过发现 = $1 但我不要得到它,它没有工作。谢谢,这让我更接近了!
    【解决方案2】:

    看起来你的正则表达式很贪心。

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

    【讨论】:

    • 有趣,我没有考虑过。但是...我认为因为 awk 是面向行的,所以它不会评估其他行的内容并吃掉整个文本块。但你是对的,我不应该使用 .* - 我用 [0-9]{1,4} 替换它,但不幸的是我仍然得到相同的行为。不过谢谢
    【解决方案3】:

    试试这个:

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

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

    【讨论】:

      【解决方案4】:

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

      awk -F ' ss:Width="252"' -v r=' ss:Width="140"' '!p && NF>1{p=1; $1 = $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 &amp;&amp; NF&gt;1 将替换值 r 用于搜索文本的第一个实例。

      【讨论】: