从DNS区域文件提取源域的脚本不会跳过前导空格的行

时间:2019-04-15 21:07:41

标签: python regex

我正在清理DNS区域文件,以提取仅包含CNAMEA记录的源域,并消除所有注释,TXTMXSRV记录。最重要的是,我想使这个过程自动化。

我设法创建了一个RegEx,该RegEx可用于在Sublime Text中执行“查找和替换”,并且需要进行几次迭代才能实现所需的结果。

让我们以以下示例DNS区域文件为例:

$ORIGIN example.com.
@                      3600 SOA   ns1.p30.dynect.net. (
                              zone-admin.dyndns.com.     ; address of responsible party
                              2016072701                 ; serial number
                      86400 IN  NS  ns1.p30.dynect.net.
                       3600 IN  MX  10 mail.example.com.
                         60 IN  A   204.13.248.106
abc                        TXT      "v=spf1 includespf.dynect.net ~all"
mail                        IN  A        204.13.248.106
vpn                         IN  TXT    v=spf1 includespf.dynect.net ~all"
vpn2                      IN  MX     v=spf1 includespf.dynect.net ~all"
webapp                      IN  A        216.146.46.10
#webapp1                  IN  A       216.146.46.10
xyz                         IN  CNAME     example.com.
webapp                      IN  SRV     216.146.46.11
;webapp2                  IN    A         216.146.46.11

第1步

将此用于“查找”

(^;.*)|(^#.*)|(^\$.*)|(^@.*)|(.*IN\h+MX.*)|(.*IN\h+TXT.*)|(.*IN\h+SRV.*)|(.*IN\h+NS.*)|(.*\h+TXT.*)|(\h.+)|(^[\n\r\h]+)

然后什么也不要替换。

这将清除区域文件,并仅显示指向CNAMEA记录的源域。

第2步

将以下内容用于“查找”

(.+$)

并用

替换
\1.example.com

结果是带有源域的源域列表:

mail.example.com
webapp.example.com
xyz.example.com


我现在正尝试编写一个Python脚本,对给定的区域文件执行上述操作,并将其输出到.txt文件中。

Wiktor Stribiżew是Stack Overflow的RegEx和Python之神,它帮助我编写了以下内容:

import re

regex = re.compile(r'^(?:\s+|[;#$@].*)|.*IN\s+(?:MX|TXT|SRV|NS).*|.*\s+TXT.*|\s.+')
with open('1.txt', 'r',encoding='UTF8') as dns:
    with open('2.txt', 'w',encoding='UTF8') as output:
        for line in dns:
            if line.strip():
                line = regex.sub('', line.strip())
                if line:
                    output.write("{}.example.com\n".format(line))

不幸的是,脚本的输出是这样的:

zone-admin.dyndns.com..example.com
2016072701.example.com
60.example.com
mail.example.com
webapp.example.com
xyz.example.com

脚本不会忽略以空格开头的行。我在做什么错了?

1 个答案:

答案 0 :(得分:2)

看起来您有两个错误,每个错误都会导致错误使用以空格开头的行。


第一个错误是在语句中

line = regex.sub('', line.strip())

会在将行传递到sub()方法之前删除所有前导空格 。因此,正则表达式永远不会看到以空格开头的任何行。

要解决此问题,在调用strip()方法之后,必须先调用sub()方法:

line = regex.sub('', line).strip()

请注意,strip()的全部原因是为了删除尾随的换行符,将line设置为空字符串以忽略行。可以使用一个简单的测试来检查这一点,因为空字符串是虚假的。

作为替代方案,可以省略此调用,而可以修改正则表达式以删除换行符。 (这可以通过用.*替换所有“跟踪” [\s\S]*来完成。)


第二个错误是在您的正则表达式中,它仅匹配行的前导空白部分,而不是整个行。这将导致sub()方法从本质上剥离前导空格!
Demo 1 1

regex = re.compile(r'^(?:\s+|[;#$@].*)|.*IN\s+(?:MX|TXT|SRV|NS).*|.*\s+TXT.*|\s.+')
                         ↑_↑
                          |
  only matches the leading white-space part, not the whole line


快速解决方案是向前推进非捕获组的结束括号:
Demo 2 1

regex = re.compile(r'^(?:\s+|[;#$@]).*|.*IN\s+(?:MX|TXT|SRV|NS).*|.*\s+TXT.*|\s.+')
                                   ↑ ↓
                                    ←


请注意,可以通过意识到可以在字符类内部移动空白元字符\s来创建一个更简单的正则表达式,并且我们只需要检查该行的第一个字符即可:
Demo 3 1

regex = re.compile(r'^[\s;#$@].*|.*IN\s+(?:MX|TXT|SRV|NS).*|.*\s+TXT.*|\s.+')


最后,通过使用否定先行匹配而不匹配 not 指向CNAME或A记录的每条非注释,无前导空格行,可以实现进一步的简化,而不是彻底而详尽地指向非CNAME /非A记录的匹配行:
Demo 4 1

regex = re.compile(r'^(?:[\s;#$@]|(?!.*IN\s+[AC])).*|\s.+')

或者,如果您希望减少嵌套(加上一个较短的字符;-)):
Demo 5 1

regex = re.compile(r'^[\s;#$@].*|^(?!.*IN\s+[AC]).*|\s.+')


这是使用上面的最后一个正则表达式的代码的完整工作版本:

import re

regex = re.compile(r'^[\s;#$@].*|^(?!.*IN\s+[AC]).*|\s.+')
with open('1.txt', 'r',encoding='UTF8') as dns:
    with open('2.txt', 'w',encoding='UTF8') as output:
        for line in dns:
            if line.strip():
                line = regex.sub('', line).strip()
                if line:
                    output.write("{}.example.com\n".format(line))

1 所有演示正则表达式均已进行了调整(最后一个空格元字符\s已替换为空格)以允许多行标志被设置为用于显示所有替换完成的结果行(在“替换”框中)。这不会影响正则表达式的功能,因为测试字符串仅包含空格和换行符,而没有其他空格。