这个问题以多种形式反复出现,有许多不同的多字符分隔符,所以恕我直言是值得一个规范的答案.
给定一个输入文件:
<foo> .. 1 <foo> .. a<2 .. </foo> .. </foo> <foo> .. @{<>}@ <foo> .. 4 .. </foo> .. </foo> <foo> .. 5 .. </foo>
Run Code Online (Sandbox Code Playgroud)
如何使用与awk的非贪婪匹配在嵌套的start(<foo>)和end(</foo>)分隔符之间提取文本?
期望的输出(以任何顺序)是:
<foo> .. a<2 .. </foo>
<foo> .. 1 .. </foo>
<foo> .. 4 .. </foo>
<foo> .. @{<>}@ .. </foo>
<foo> .. 5 .. </foo>
Run Code Online (Sandbox Code Playgroud)
请注意,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>
Run Code Online (Sandbox Code Playgroud)
上面的工作原理是您选择不能出现在开始/结束字符串中的任何字符(注意它不一定是根本不能出现在输入中的字符,只是不在这些字符串中),在在这种情况下,我选择,并在输入中每次出现后@附加。A此时,每次出现 都@A代表一个字符,并且保证在输入中的任何地方@都不会出现@B或后面有任何其他内容。@
现在我们可以选择另外 2 个用于表示开始/结束字符串的字符,在本例中我选择{and },并将它们转换为一些@前缀字符串,例如@Band @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)
}
}
Run Code Online (Sandbox Code Playgroud)
显然,您实际上并不需要将编码/解码功能放在单独的函数中,为了清楚起见,我只是将其分开,以使该功能明确并与使用它的循环分开。
有关何时/如何应用上述方法的另一个示例,请参阅/sf/answers/2837811231/。