如何表示awk sub / gsub的匹配字符串的许多部分

时间:2019-05-06 12:10:00

标签: regex awk gsub

如何表示awk sub或gsub的匹配字符串的多个部分。

对于像“ ## code”这样的正则表达式,如果我想在“ ##”和“ code”之间插入一个单词,我想要一种类似VSCode语法的方式,其中$ 1代表第一部分,$ 2代表第二部分部分

sub(/(##)(code)/, "$1before$2", str)

从awk的用户手册中,我发现awk使用&表示整个匹配的字符串。如何像VSCode那样表示匹配的字符串中的一个,两个或更多部分。

  

sub(regexp,替换[,目标])   搜索目标,被视为字符串,用于与正则表达式regexp匹配的最左边,最长的子字符串。通过用替换替换匹配的文本来修改整个字符串。修改后的字符串成为目标的新值。返回进行替换的次数(零或一)。

     

regexp参数可以是regexp常量(/…/)或字符串常量(“…”)。在后一种情况下,该字符串被视为要匹配的正则表达式。有关这两种形式之间的差异以及正确编写程序的含义的讨论,请参见Compute Regexps。

     

该函数是特殊的,因为target不是简单地用于计算值,而且不仅仅是任何表达式都可以—它必须是变量,字段或数组元素,以便sub()可以在那里存储修改后的值。如果省略此参数,则默认值为使用和更改$ 0.48例如:

     

str =“水,水无处不在”   sub(/ at /,“ ith”,str)   通过用“ ith”替换最长的“ at”处最长的出现,将str设置为“枯萎,无处不在”。

     

如果替换中出现特殊字符“&”,则表示与regexp匹配的精确子字符串。 (如果正则表达式可以匹配多个字符串,则此精确子字符串可能会有所不同。)例如:

     

{ sub(/candidate/, "& and his wife"); print }

     

在每个输入行上将首次出现的“候选人”更改为“候选人及其妻子”。这是另一个示例:

用户手册的链接为here

2 个答案:

答案 0 :(得分:2)

您最好的选择是将GNU awk用于以下任何一项:

$ awk '{$0=gensub(/(##)(code)/,"\\1before\\2",1)} 1' <<<'##code'
##beforecode

$ awk 'match($0,/(##)(code)/,a){$0=a[1] "before" a[2]} 1' <<<'##code'
##beforecode

第一个只允许您移动文本段,而第二个只允许您调用函数,执行数学运算或对匹配的文本进行其他操作,然后再将其移动到原始文本中或对其进行其他操作:

$ awk 'match($0,/(##)(code)/,a){$0=length(a[1])*10 "before" toupper(a[2])} 1' <<<'##code'
20beforeCODE

考虑了一下之后,我不知道如何仅使用POSIX awk结构以任何合理的方式获得所需的行为。这是我尝试过的(matches()函数):

$ cat tst.awk
BEGIN {
    str = "foobar"
    re  = "(f.*o)(b.*r)"
    printf "\nre \"%s\" matching string \"%s\"\n", re, str

    print "succ: gensub():  ", gensub(re,"<\\1> <\\2>",1,str)
    print "succ: match():   ", (match(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
    print "succ: matches(): ", (matches(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")

    str = "foofoo"
    re  = "(f.*o)(f.*o)"
    printf "\nre \"%s\" matching string \"%s\"\n", re, str

    print "succ: gensub():  ", gensub(re,"<\\1> <\\2>",1,str)
    print "succ: match():   ", (match(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
    print "fail: matches(): ", (matches(str,re,a) ? "<" a[1] "> <" a[2] ">" : "")
}

function matches(str,re,arr,    start,tgt,n,i,segs) {
    delete arr
    if ( start=match(str,re) ) {
        tgt = substr($0,RSTART,RLENGTH)
        n = split(re,segs,/[)(]+/) - 1
        for (i=1; RSTART && (i < n); i++) {
            if ( match(str,segs[i+1]) ) {
                arr[i] = substr(str,RSTART,RLENGTH)
                str = substr(str,RSTART+RLENGTH)
            }
        }
    }
    return start
}

$ awk -f tst.awk

re "(f.*o)(b.*r)" matching string "foobar"
succ: gensub():   <foo> <bar>
succ: match():    <foo> <bar>
succ: matches():  <foo> <bar>

re "(f.*o)(f.*o)" matching string "foofoo"
succ: gensub():   <foo> <foo>
succ: match():    <foo> <foo>
fail: matches():  <foofoo> <>

但是对于第二种情况当然不起作用,因为f.*o的第一个RE段与整个字符串foofoo匹配,并且如果您尝试采用RE段,当然也会发生相同的情况相反。我也考虑过像上面那样获得RE段,但是随后从传入的字符串中一次构建一个字符,并将第一个RE段与THAT进行比较,直到匹配为止,因为这将是RE段中最短的匹配字符串,但对于像这样的字符串+ RE将会失败:

str='foooobar'
re='(f.*o)(b.*r)'

因为f.*o确实需要与foo匹配的fooooo

所以-我想您需要继续进行迭代(注意迭代的方向-我期望从头到尾都是正确的),直到将字符串拆分为各个段为止,每个段都与左侧的每个RE段匹配-最长的时尚。似乎需要很多工作!

答案 1 :(得分:0)

使用GNU awk时,可以为此使用gensub。如果没有gensub进行任何通用awk,它将变得更加乏味。该过程可能是这样的:

ere="(ere1)(ere2)"
match(str,ere)
tmp=substr(str,RSTART,RLENGTH)
match(tmp,"ere1"); part1=substr(tmp,RSTART,RLENGTH)
part2=substr(tmp,RLENGTH)
sub(ere,part1 "before" part2,str)

此问题是,它将无法始终正常工作,您必须对其进行一些设计。由于ERE的贪婪,可以创建一个简单的失败。

str="foocode"
ere="(f.*o)(code)"
match(str,ere)                    # finds "foocode"
tmp=substr(str,RSTART,RLENGTH)    # tmp <: "foocode"
match(tmp,"(f.*o)");              # greedy "fooco"
part1=substr(tmp,RSTART,RLENGTH)  # part1 <: "fooco"
part2=substr(tmp,RLENGTH)         # part2 <: "de"
sub(ere,part1 "before" part2,str) # :> "foocobeforede