与awk(gawk)的gsub问题

时间:2013-02-19 10:25:24

标签: windows awk

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

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

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

例如:

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.

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

所以,我尝试了“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 / expression中表示变量“a”?

3 个答案:

答案 0 :(得分:2)

awk版本:

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

答案 1 :(得分:2)

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

所以在awk中,缺少perl的“gensub()”评估功能及其有状态正则表达式s///e修饰符(由Steve使用),剩下的最佳选择是将行分成块(抬头匹配尾巴)并再次将它们重新组合在一起:

/g

这使用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} 作为epxression而不是match()模式,因此您可以使用变量。 (您也可以使用“//”,但此代码中使用了($0 ~ a) { ... }的结果,因此请勿在此处尝试。)

您可以在命令行中定义match()j

a支持gawk,相当于perlre的\y,并支持\b\<明确匹配单词的开头和结尾,请注意从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)索引将会被1关闭。如果您事先知道,或者通过字符串向后工作,可以解决这个问题。
  • 如果用子串“gensub()”或“i”替换子串“ii”,则会出现类似的问题(导致无限循环,因为ii(i)保持不变找到新的比赛)

强有力地处理这两个条件并不值得代码。

答案 2 :(得分:1)

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

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

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

这也可以是shell var:

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.