给定一个输入文件:
<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可以是任何多字符字符串,它们之间的文本可以是除字符串之外的任何字符串,包括属于这些字符串的字符,例如<或>这个例子中的字符.
解决方法
$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.