【问题标题】:gsub issue with awk (gawk)awk 的 gsub 问题(gawk)
【发布时间】:2013-02-03 23:53:32
【问题描述】:

我需要在文本文件中搜索一个字符串,并进行替换,其中包含一个随着每次匹配而递增的数字。

要“找到”的字符串可以是单个字符、单词或短语。

替换表达式并不总是相同的(如下面的示例所示),但总是包含一个递增的数字(变量)。

例如:

1) 我有一个名为“data.txt”的测试文件。该文件包含:

Now is the time
for all good men
to come to the
aid of their party.

2) 我将 awk 脚本放在名为“cmd.awk”的文件中。该文件包含:

/f/ {sub ("f","f(" ++j ")")}1

3) 我这样使用 awk:

awk -f cmd.awk data.txt

在这种情况下,输出如预期:

Now is the time
f(1)or all good men
to come to the
aid of(2) their party.

当一行中有多个匹配项时,问题就出现了。例如,如果我正在搜索字母“i”,例如:

/i/ {sub ("i","i(" ++j ")")}1

输出是:

Now i(1)s the time
for all good men
to come to the
ai(2)d of their party.

这是错误的,因为它不包括“时间”或“他们”中的“我”。

所以,我尝试使用“gsub”而不是“sub”,例如:

/i/ {gsub ("i","i(" ++j ")")}1

输出是:

Now i(1)s the ti(1)me
for all good men
to come to the
ai(2)d of thei(2)r party.

现在它会替换所有出现的字母“i”,但插入的数字对于同一行上的所有匹配项都是相同的。

期望的输出应该是:

Now i(1)s the ti(2)me
for all good men
to come to the
ai(3)d of thei(4)r party.

注意:数字不会总是以“1”开头,所以我可能会像这样使用 awk:

awk -f cmd.awk -v j=26 data.txt

获取输出:

Now i(27)s the ti(28)me
for all good men
to come to the
ai(29)d of thei(30)r party.

为了清楚起见,替换中的数字并不总是在括号内。并且替换并不总是包含匹配的字符串(实际上它非常罕见)。

我遇到的另一个问题是......

我想为“搜索字符串”使用一个 awk 变量(不是环境变量),所以我可以在 awk 命令行中指定它。

例如:

1) 我将 awk 脚本放在名为“cmd.awk”的文件中。该文件包含以下内容:

/??a??/ {gsub (a,a "(" ++j ")")}1

2) 我会像这样使用 awk:

awk -f cmd.awk -v a=i data.txt

获取输出:

Now i(1)s the ti(2)me
for all good men
to come to the
ai(3)d of thei(4)r party.

这里的问题是,如何在 /search/ 表达式中表示变量“a”?

【问题讨论】:

    标签: windows awk


    【解决方案1】:

    awk 版本:

    awk '{for(i=2; i<=NF; i++)$i="(" ++k ")" $i}1' FS=i OFS=i
    

    【讨论】:

    • 我不得不使用引号来使它在 Windows 命令行上工作,这就是我想出的:awk "BEGIN{FS=\"i\"; OFS=\"i\"; k=0}{for(i=2; i&lt;=NF; i++)$i=\"(\" ++k \")\" $i}1" "data.txt"。你能解释一下为什么变量“i”的某些用法以“$”(“$i”)开头,而有些则不是吗?
    • 变量不使用 $ 符号,但字段 ($1, $2, ..) 使用,记录本身 ($0) 也是如此。但是,例如,如果 i=5,则 $i 表示 $5。
    • @KevinFegan 不要在 Windows 的命令行上使用 awk 脚本,否则您会遇到引用和其他问题。而是将脚本放在一个名为“foo.awk”的文件中,并将其作为 awk -f foo.awk data.txt 运行。您可能需要考虑安装 cygwin 以避免 Windows 脚本的噩梦。
    • @EdMorton 谢谢,我同意。如我在问题中给出的示例所示,我通常将 awk 脚本放在一个文件中并使用-f 运行 awk(在 Windows 上)。非常非常简单的情况除外。
    【解决方案2】:

    gensub() 在这里听起来很理想,它允许您替换第 N 个匹配项,所以听起来像是一个解决方案是在 do{}while() 循环中迭代字符串,一次替换一个匹配项并递增 j。如果替换不包含原始文本(或更糟糕的是,包含多次),这种简单的gensub() 方法将不起作用,请参见下文。

    因此,在 awk 中,缺少 perl 的“s///e”评估功能及其有状态的正则表达式 /g 修饰符(史蒂夫使用),剩下的最佳选择是将行分成块(head, match, tail) 并将它们重新粘在一起:

    BEGIN { 
        if (j=="") j=1
        if (a=="") a="f"
    }
    match($0,a) { 
        str=$0; newstr=""
        do {
             newstr=newstr substr(str,1,RSTART-1) # head
             mm=substr(str,RSTART,RLENGTH)        # extract match
             sub(a,a"("j++")",mm)                 # replace
             newstr=newstr mm 
             str=substr(str,RSTART+RLENGTH)       # tail
        } while (match(str,a))
        $0=newstr str     
    }
    {print}
    

    这使用match() 作为表达式而不是// 模式,因此您可以使用变量。 (你也可以只使用“($0 ~ a) { ... }”,但是这段代码中使用了match()的结果,所以这里不要尝试。)

    您可以在命令行中定义ja

    gawk 支持\y,相当于perlre 的\b,还支持\&lt;\&gt; 显式匹配单词的开头和结尾,只需注意在a 中添加额外的转义符unix 命令行(我不太确定 Windows 可能需要或允许什么)。


    有限gensub()

    如上所述:

    match($0,a) {
        idx=1; str=$0
        do {
            prev=str
            str=gensub(a,a"(" j ")",idx++,prev)
        } while (str!=prev && j++)
        $0=str
    }
    

    这里的问题是:

    • 如果您将子字符串“i”替换为子字符串“k”或“k(1)”,则下一个匹配项的gensub() 索引将关闭 1。如果您知道,您可以解决此问题提前,或者通过字符串向后工作。
    • 如果将子字符串“i”替换为子字符串“ii”或“ii(i)”,则会出现类似问题(导致无限循环,因为gensub() 不断寻找新匹配项)

    稳健地处理这两种情况并不值得编写代码。

    【讨论】:

    • 你说This won't work if the replacement does NOT contain the original text。你能解释一下原因,并提供一个gensub() 的例子吗?另外,如果我只想替换一行中的第一个匹配项,您能否描述一下我如何在 /search/ 表达式中表示变量“a”,例如:/??a??/ {sub (a,a "(" ++j ")")}1
    • gensub() 已添加。如上所述使用match() {...},或者由于“//”实际上等同于($0 ~ //),您可以使用($0 ~ a) {...},其中a是正则表达式变量(尽管在变量中不使用前导/尾随/ )。如果您不需要捕获或其他任何东西,@Scrutinizer 的方法更简单。
    • if you replace "i" with "k(1)" - 我看不到你在哪里定义变量“k”...或者“k”只是一个字符串文字?
    【解决方案3】:

    我并不是说使用awk 无法做到这一点,但我强烈建议您转向更强大的语言。请改用perl

    要包含从 26 开始的字母 i 的计数,请尝试:

    perl -spe 's:i:$&."(".++$x.")":ge' -- -x=26 data.txt
    

    这也可以是一个 shell 变量:

    var=26
    perl -spe 's:i:$&."(".++$x.")":ge' -- -x=$var data.txt
    

    结果:

    Now i(27)s the ti(28)me
    for all good men
    to come to the
    ai(29)d of thei(30)r party.
    

    要包含特定单词的计数,请在单词周围添加单词边界(即\b),尝试:

    perl -spe 's:\bthe\b:$&."(".++$x.")":ge' -- -x=5 data.txt
    

    结果:

    Now is the(6) time
    for all good men
    to come to the(7)
    aid of their party.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-16
      • 2016-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多