如何使用awk使用非贪婪的regexp在嵌套分隔符中提取数据

这个问题以多种形式反复出现,有许多不同的多字符分隔符,所以恕我直言是值得一个规范的答案.

给定一个输入文件

<foo> .. 1 <foo> .. a<2 .. </foo> .. </foo> <foo> .. @{<>}@ <foo> .. 4 .. </foo> .. </foo> <foo> .. 5 .. </foo>

如何使用与awk的非贪婪匹配在嵌套的start(< foo>)和end(< / foo>)分隔符之间提取文本?

期望的输出(以任何顺序)是:

<foo> .. a<2 .. </foo>
<foo> .. 1  .. </foo>
<foo> .. 4 .. </foo>
<foo> .. @{<>}@  .. </foo>
<foo> .. 5 .. </foo>

请注意,start或end可以是任何多字符字符串,它们之间的文本可以是除字符串之外的任何字符串,包括属于这些字符串的字符,例如<或>这个例子中的字符.

解决方法

主要挑战是,由于awk只支持贪婪匹配,因此无法编写< foo>.*< / foo>的任何变体.将停在第一个< / foo>在线而不是最后的< / foo>.解决方案是将每个开始和结束字符串转换为单个字符,这些字符不能出现在输入中,因此您可以编写x [^ xy] * y,其中x和y是那些开始/结束字符,但是如何选择可以使用的字符出现在输入中?你没有 – 你做一个
$cat nonGreedy.awk
{
    $0 = encode($0)
    while ( match($0,/({[^{}]*})/) ) {
        print decode(substr($0,RSTART,RLENGTH))
        $0 = substr($0,1,RSTART-1) substr($0,RSTART+RLENGTH)
    }
}
function encode(str) {
    gsub(/@/,"@A",str)
    gsub(/{/,"@B",str); gsub(/}/,"@C",str)
    gsub(/<foo>/,"{",str); gsub(/<\/foo>/,"}",str)
    return str
}
function decode(str) {
    gsub(/}/,"</foo>",str); gsub(/{/,"<foo>",str)
    gsub(/@C/,str); gsub(/@B/,str)
    gsub(/@A/,"@",str)
    return str
}

$awk -f nonGreedy.awk file
<foo> .. a<2 .. </foo>
<foo> .. 1  .. </foo>
<foo> .. 4 .. </foo>
<foo> .. @{<>}@  .. </foo>
<foo> .. 5 .. </foo>

上面的工作是你选择任何不能出现在开始/结束字符串中的字符(注意它不一定是一个根本不能出现在输入中的字符,只是不在那些字符串中),这种情况我选择@,并在每次出现后在输入中附加一个A.此时,每次出现的@A代表一个@字符,并且保证不会出现@B或@,后跟输入中的任何其他位置.

现在我们可以选择其他2个我们想要用来表示开始/结束字符串的字符,在这种情况下我选择{和},并将它们转换为@ -prefixed字符串,如@B和@C,此时每次出现的@B代表一个{字符,@ C代表一个}字符,并且输入中没有{s或} s.

现在剩下要做的就是找到我们想要提取的字符串,转换每个起始字符串< foo>到我们选择的开始字符{,以及每个结束字符串< / foo>到最后一个字符}然后我们可以使用{[^ {}] *}的简单正则表达式来表示< foo>.*< / foo>的非贪婪版本.

当我们找到每个字符串时,我们只是以相反的顺序展开我们在上面所做的转换(请注意,您必须完全按照将它们应用于整个记录的相反顺序展开每个匹配字符串的替换),因此{返回< foo>并且@B返回{,并且@A返回@等等,我们有该字符串的原始文本.

以上将适用于任何awk.如果您的开始/结束字符串包含RE元字符,那么您必须转义它们或使用while(index(substr()))循环而不是gsub()来替换它们.

请注意,如果您确实使用gawk并且标签没有嵌套,那么您可以完全按照上面的方式保留2个函数,并将脚本的其余部分更改为:

BEGIN { FPAT="{[^{}]*}" }
{
    $0 = encode($0)
    for (i=1; i<=NF; i++) {
        print decode($i)
    }
}

显然,您并不需要将编码/解码功能放在单独的函数中,我只是将其分离出来以使该功能明确,并与使用它的循环分开以便清楚.

有关何时/如何应用上述方法的另一个示例,请参见https://stackoverflow.com/a/40540160/1745001.

相关文章

/etc/sysctl.conf这个目录主要是配置一些系统信息,/etc/sys...
1.作用 useradd或adduser命令用来建立用户帐号和创建用户的起...
它们都是多模式编辑器,不同的是vim 是vi的升级版本,它不仅...
不管是我们在安装软件还是监测软件的使用性能,我们都要随时...
装好Tomcat7后,发现除了本机能访问外界访问不了,岂有此理。...
修改防火墙配置需要修改 /etc/sysconfig/iptables 这个文件,...