我需要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()
的结果,所以不要在这里试试。)
你可以在命令行上定义j
和a
。
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 }
这里的问题是:
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.