如何在awk中将一串文本捕获为单个字段?

时间:2013-12-15 01:10:17

标签: regex bash awk

我目前正在尝试编写一个awk脚本来处理一堆DNS区域文件。但是,我遇到了一个问题,其中某些DNS区域记录(即TXT记录形式的SPF和DKIM记录)中有空格,这会导致awk将区域记录的数据解释为多个字段。但是,他们共享的一个一致特征是TXT记录的数据始终用双引号括起来,并且它始终是最后一个实际字段。

以下是一个示例DNS区域,其中包含几条SPF记录,仅用于显示输入文件的结构:

; cPanel first:11.34.1.7 (update_time):1380693490 Cpanel::ZoneFile::VERSION:1.3 hostname:bentley.websitewelcome.com latest:11.36.2.4
; Zone file for digdeepdns.net
$TTL 14400
digdeepdns.net. 86400   IN  SOA ns1.digdeepdns.net. slucas.digdeepdns.net.  (
                        2013100200 ;Serial Number
                        14400 ;refresh
                        7200 ;retry
                        3600000 ;expire
                        14400 ;minimum
    )
digdeepdns.net. 86400   IN  NS  ns1.digdeepdns.net.
digdeepdns.net. 86400   IN  NS  ns2.digdeepdns.net.
digdeepdns.net. 14400   IN  A   192.185.57.22
localhost   14400   IN  A   127.0.0.1
digdeepdns.net. 14400   IN  MX  0   digdeepdns.net.
mail    14400   IN  CNAME   digdeepdns.net.
www 14400   IN  CNAME   digdeepdns.net.
ftp 14400   IN  A   192.185.57.22
digdeepdns.net. IN TXT "v=spf1 ip4:70.84.243.130 a mx ip4:192.185.57.216 include:websitewelcome.com ~all"
cpanel  14400   IN  A   192.185.57.22
webmail 14400   IN  A   192.185.57.22
whm 14400   IN  A   192.185.57.22
webdisk 14400   IN  A   192.185.57.22
ns1 14400   IN  A   192.185.57.216
ns2 14400   IN  A   192.185.57.22
hg 14400 IN A  192.185.57.22
www.hg 14400 IN A  192.185.57.22
hg IN TXT "v=spf1 ip4:70.84.243.130 +a +mx +ip4:192.185.57.216 ?all"
webdisk.hg IN A 192.185.57.22

每个记录的字段分隔符可以是制表符或空格字符串。您还可以看到并非所有记录都具有明确定义的TTL,因此我不能假设$ 2将是一个数字而$ 3将是文字'IN'。 SOA之后所有行中唯一的共同线程是$ 1,/[-_0-9a-z.]+/可以轻松捕获。我不打算进行精确和验证;这些DNS区域是由一个脚本生成的,几乎可以保证它们符合RFC 1035标准。

DNS区域当前正由以下awk脚本处理:

#! /opt/local/bin/awk -f
BEGIN { OFS = "\t" }
NR < 11 { print }
NR > 10 && /("[^"]+")/ { print }

到目前为止,这是脚本的示例输出,遗憾的是忽略了每一行 - 除了 - 这两个是SPF记录,但至少证明正则表达式/("[^"]+")/就像一个魅力:

; cPanel first:11.34.1.7 (update_time):1380693490 Cpanel::ZoneFile::VERSION:1.3 hostname:bentley.websitewelcome.com latest:11.36.2.4
; Zone file for digdeepdns.net
$TTL 14400
digdeepdns.net. 86400   IN  SOA ns1.digdeepdns.net. slucas.digdeepdns.net.  (
                        2013100200 ;Serial Number
                        14400 ;refresh
                        7200 ;retry
                        3600000 ;expire
                        14400 ;minimum
    )
digdeepdns.net. IN TXT "v=spf1 ip4:70.84.243.130 a mx ip4:192.185.57.216 include:websitewelcome.com ~all"
hg IN TXT "v=spf1 ip4:70.84.243.130 +a +mx +ip4:192.185.57.216 ?all"

区域文件的前10行可以忽略(因此脚本直接打印出来);无论如何它们都需要一些手动处理。但是第11行和以后需要更好的对齐,我打算用printf来完成。

我首先开始简单地弄清楚我在做什么,但最终我将使用printf替换带有标签的空格,以便我可以使列更整齐地对齐。但是,由于IFS必须是空白,这就带来了挑战,即我的DNS区域中少数TXT记录中的文本数据必须以某种方式全局整合并解释为与printf一起使用的单个输入字段。因此,正则表达式可以捕获双引号内的整个文本。

我特意在awk中寻求解决方案(或者至少是一个非常有用的暗示,可以引导我找到解决方案),因为这种学习体验专门针对awk。我确信我可以找到一种可以在sed中轻松完成的方法,但这并不是我最终目标的重点。

我在这个问题上有点过头了,我真的可以帮忙。

编辑:

在推荐时,这里是一个所需输出的样本(表格可能有点笨拙,但基本要点是第1列有3个选项卡空间,所有其他选项卡有1个选项卡):

; cPanel first:11.34.1.7 (update_time):1380693490 Cpanel::ZoneFile::VERSION:1.3 hostname:bentley.websitewelcome.com latest:11.36.2.4
; Zone file for digdeepdns.net
$TTL 14400
digdeepdns.net. 86400   IN  SOA ns1.digdeepdns.net. slucas.digdeepdns.net.  (
                        2013100200 ;Serial Number
                        14400 ;refresh
                        7200 ;retry
                        3600000 ;expire
                        14400 ;minimum
    )
digdeepdns.net.     86400   IN  NS  ns1.digdeepdns.net.
digdeepdns.net.     86400   IN  NS  ns2.digdeepdns.net.
digdeepdns.net.     14400   IN  A   192.185.57.22
localhost           14400   IN  A   127.0.0.1
digdeepdns.net.     14400   IN  MX  0   digdeepdns.net.
mail            14400   IN  CNAME   digdeepdns.net.
www             14400   IN  CNAME   digdeepdns.net.
ftp             14400   IN  A   192.185.57.22
digdeepdns.net.         IN  TXT "v=spf1 ip4:70.84.243.130 a mx ip4:192.185.57.216 include:websitewelcome.com ~all"
cpanel          14400   IN  A   192.185.57.22
webmail         14400   IN  A   192.185.57.22
whm             14400   IN  A   192.185.57.22
webdisk         14400   IN  A   192.185.57.22
ns1             14400   IN  A   192.185.57.216
ns2             14400   IN  A   192.185.57.22
hg              14400   IN  A   192.185.57.22
www.hg          14400   IN  A   192.185.57.22
hg                  IN  TXT "v=spf1 ip4:70.84.243.130 +a +mx +ip4:192.185.57.216 ?all"
webdisk.hg              IN  A   192.185.57.22

最终的问题是我如何让awk处理前3-4个字段(取决于$ 2是数字还是'IN')和正常情况一样,然后对于字段5+,它也需要是条件的。引号中的任何内容都必须作为单个字段处理。我非常喜欢建议或指示,以帮助我找到解决方案。

编辑#2:

在一个有些相关且同时无关的侧切线上。我似乎无法找到一个明确的答案,关于是否可以将模式{statement}对作为模式{statement}子句中的语句嵌套,如下所示:

patternA {
    patternAA { statements }
    patternAB { statements }
}
patternB {
    patternBA { statements }
    patternBB { statements }
}

这可能在awk中吗?如果是这样,这可能有助于我弄清楚如何做这个脚本。

2 个答案:

答案 0 :(得分:1)

可能有更好的方法来写这个,但在这里:

NR < 11 { print; next }
{
    hname = $1
    if ($2 == "IN") {
        port = ""
        dom  = $3
        addri = 4 
    }   
    else {
        port = $2
        dom  = $4
        addri = 5 
    }   
    addr = $addri
    if (dom == "TXT") {
        for (i = addri + 1; i <= NF; i++) {
            addr = addr" "$i
            if ( index($i, "\"") != 0)
                break
        }
    }   
    printf("%-30s\t%s\t%s\t%-10s\t%s\n", hname, port, "IN", dom, addr)
}

答案 1 :(得分:0)

#! /opt/local/bin/awk -f    
BEGIN{FS=" IN "}
NR<11{print;next}
{ split($1,a,OFS);printf "%-30s%-10s\t%s\t",a[1],a[2],FS
  l=split($2,b,OFS);printf "%-10s\t",b[1]
  for (i=2;i<=l;i++) printf b[i] OFS;printf RS
}


$ awk -f b.awk infile
; cPanel first:11.34.1.7 (update_time):1380693490 Cpanel::ZoneFile::VERSION:1.3 hostname:bentley.websitewelcome.com latest:11.36.2.4
; Zone file for digdeepdns.net
$TTL 14400
digdeepdns.net. 86400   IN  SOA ns1.digdeepdns.net. slucas.digdeepdns.net.  (
                        2013100200 ;Serial Number
                        14400 ;refresh
                        7200 ;retry
                        3600000 ;expire
                        14400 ;minimum
    )
digdeepdns.net.               86400              IN     NS              ns1.digdeepdns.net.
digdeepdns.net.               86400              IN     NS              ns2.digdeepdns.net.
digdeepdns.net.               14400              IN     A               192.185.57.22
localhost                     14400              IN     A               127.0.0.1
digdeepdns.net.               14400              IN     MX              0 digdeepdns.net.
mail                          14400              IN     CNAME           digdeepdns.net.
www                           14400              IN     CNAME           digdeepdns.net.
ftp                           14400              IN     A               192.185.57.22
digdeepdns.net.                                  IN     TXT             "v=spf1 ip4:70.84.243.130 a mx ip4:192.185.57.216 include:websitewelcome.com ~all"
cpanel                        14400              IN     A               192.185.57.22
webmail                       14400              IN     A               192.185.57.22
whm                           14400              IN     A               192.185.57.22
webdisk                       14400              IN     A               192.185.57.22
ns1                           14400              IN     A               192.185.57.216
ns2                           14400              IN     A               192.185.57.22
hg                            14400              IN     A               192.185.57.22
www.hg                        14400              IN     A               192.185.57.22
hg                                               IN     TXT             "v=spf1 ip4:70.84.243.130 +a +mx +ip4:192.185.57.216 ?all"
webdisk.hg                                       IN     A               192.185.57.22