gsub问题与awk(gawk)

我需要search一个string的文本文件,并进行replace,包括一个数字,每增加​​一个匹配。

被“find”的string可以是单个字符,也可以是单词或短语。

replaceexpression式不会总是一样的(就像我在下面的例子中那样),但是总是会包含一个数字(variables)。

例如:

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

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. 

当一条线上有多个匹配时,问题就来了。 例如,如果我正在search字母“我”就像:

 /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. 

现在它replace了所有出现的字母“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 -vj=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. 

而要清楚的是,replace中的数字并不总是在括号内。 并且replace将不总是包含匹配的string(实际上它将是相当罕见的)。

我遇到的另一个问题是…

我想为“searchstring”使用awkvariables(不是环境variables),所以我可以在awk命令行中指定它。

例如:

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

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

2)我会这样使用awk:

 awk -f cmd.awk -va=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中表示variables“a”?

awk版本:

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

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

所以在awk中,缺少perl的“ s///e ”评估功能,以及它的有状态正则表达式/g修饰符(用于Steve),最好的选择是将行分成块( )再次回到一起:

 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()作为epxression而不是//模式,所以你可以使用一个变量。 (你也可以使用“ ($0 ~ a) { ... } ”,但是在这个代码中使用了match()的结果,所以不要在这里试试。)

你可以在命令行上定义ja

gawk支持\y ,它相当于perlre的\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 } 

这里的问题是:

  • 如果用substring“ k ”或“ k(1) ”替换子字符串“ i ”,那么下一个匹配的gensub()索引将会被gensub() 1.如果您事先知道该工作,反而通过字符串。
  • 如果用子字符串“ ii ”或“ ii(i) ”替换子字符串“ i ”,则会出现类似的问题(导致无限循环,因为gensub()不断发现新匹配)

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

我并不是说这不能用awk来完成,但我强烈建议转向更强大的语言。 改用perl

要包括从26开始的字母数,请尝试:

 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.