首页 > 解决方案 > 如何单独处理输入文件的块?

问题描述

我正在尝试读取防火墙配置并使用 Python 操作它定义的对象,然后更改对象名称并将其导出到新的配置文件。输入示例:

edit "host1"
  set subnet 10.0.0.10 255.255.255.255  
next
edit "host2"
  set subnet 10.0.0.11 255.255.255.255 
next

读取文件时,我需要一种方法来遍历“edit [...]”的每个实例,并设置以下属性(可以是多个)。本质上,包含“edit”的行表示新对象的开始,“next”表示该对象的结束。目标是检查每个对象的内容并根据它的内容重命名它,基于前面的示例,输出将是:

edit "10.0.0.10"
  set subnet 10.0.0.10 255.255.255.255  
next
edit "10.0.0.11"
  set subnet 10.0.0.11 255.255.255.255 
next

操作文本本身应该不是什么大问题,但我目前一直在寻找一种方法来单独处理配置的每个块。

我曾考虑将输入转换为 XML,然后使用 ElementTree,但我希望有一种更优雅的方式来完成它,而无需我因为缺乏编程经验而没有看到的额外步骤。

提前感谢您提供有关如何解决此问题的任何意见。

标签: pythonautomation

解决方案


递归下降解析器时间!

如果这些配置文件真的那么简单,你可以使用这个语法:

start: block*
block: EDIT HOSTNAME rule* NEXT
HOSTNAME: /"[^"]"/
rule: SET SUBNET IP IP
IP: /\d+\.\d+\.\d+\.\d+/

然后解析器看起来像这样。edit它使用规则中的主机自动重写所有块set

import re


class Parser:
    def __init__(self, tokens: list):
        self.tokens = tokens

    def _next(self) -> str:
        token = self.tokens[0]
        del self.tokens[0]

        return token

    def _peek(self) -> str:
        return self.tokens[0]

    def consume(self, value: str) -> str:
        token = self._next()

        if token == value:
            return token

        raise SyntaxError(f"Could not consume {value!r} given {token!r} + {self.tokens}")

    def consume_regex(self, regex) -> str:
        token = self._next()

        match, = regex.fullmatch(token).groups()

        return match

    def parse(self):
        edits = self.parse_start()

        assert not self.tokens

        return edits

    def parse_start(self):
        "start: edit*"

        edits = []

        while self.tokens:
            edit = self.parse_edit()
            edits.append(edit)

        return edits

    def parse_edit(self) -> str:
        "edit: EDIT HOST rule NEXT"

        HOST_REGEX = re.compile(r'^"([^"]*)"$')

        _EDIT = self.consume('edit')
        _HOST = self.consume_regex(HOST_REGEX)
        _rule = self.parse_rule()
        _NEXT = self.consume('next')

        # REWRITE HERE!
        rewritten = f'''
{_EDIT} "{_rule['IP']}"
    {_rule['type']} {_rule['subnet']} {_rule['IP']} {_rule['mask']}
{_NEXT}
        '''.strip()

        return rewritten


    def parse_rule(self) -> dict:
        "rule: SET rule_set"  # other rules can be added
        tok = self._peek()

        if tok == 'set':
            return self.parse_rule_set()

        raise SyntaxError(f'Unknown rule {tok!r}')

    def parse_rule_set(self) -> dict:
        "rule_set: SET SUBNET IP MASK"

        # this regex doesn't check the validity of the IP address,
        # but it's good enough here
        IP_REGEX = re.compile(r"^(\d+\.\d+\.\d+\.\d+)$")

        _SET = self.consume('set')
        _SUBNET = self.consume('subnet')
        _IP = self.consume_regex(IP_REGEX)
        _MASK = self.consume_regex(IP_REGEX)

        return {'type': 'set', 'subnet': _SUBNET, 'IP': _IP, 'mask': _MASK}



if __name__ == '__main__':
    data = '''
edit "host1"
  set subnet 10.0.0.10 255.255.255.255  
next
edit "host2"
  set subnet 10.0.0.11 255.255.255.255 
next
    '''

    ret = Parser(data.split()).parse()

    print('\n'.join(ret))

输出:

$ python3 test.py
edit "10.0.0.10"
    set subnet 10.0.0.10 255.255.255.255
next
edit "10.0.0.11"
    set subnet 10.0.0.11 255.255.255.255
next
$ 

推荐阅读