使用pyparsing解析dhcpd.conf时重复键被覆盖问题如何解决
解决方法
核心问题出在pyparsing自带的Dict组件默认对重复key的处理逻辑是覆盖,无法自动处理多值、多块的场景。我们可以先解析出原始的键值对列表,再通过自定义后处理逻辑实现预期的字典结构,完整可运行代码如下:
from pprint import pprint from pyparsing import ( CharsNotIn, Literal, White, Word, Combine, ZeroOrMore, OneOrMore, Forward, ParseException, Group, Suppress, delimitedList, restOfLine, alphanums, pyparsing_common, ) # 基础规则定义 prop = delimitedList(Word(alphanums + "-_.!@#$%^&*\"")) ipAddress = pyparsing_common.ipv4_address | pyparsing_common.ipv6_address macAddress = Combine( Word(alphanums, exact=2) + ":" + Word(alphanums, exact=2) + ":" + Word(alphanums, exact=2) + ":" + Word(alphanums, exact=2) + ":" + Word(alphanums, exact=2) + ":" + Word(alphanums, exact=2) ) list_ipAddress = delimitedList(ipAddress) name = Word(alphanums + "-") value = list_ipAddress | macAddress | prop | CharsNotIn(";") comment = "#" + restOfLine LBRACE, RBRACE, WHITE_SPACE = map(Suppress, "{} ") struct = Forward() # 各配置项规则 hw_eth = Group(Combine(Literal("hardware") + White(" ") + name) + macAddress) name_value = Group(name + value) subnet = Group(Combine("subnet" + White(" ") + ipAddress + White(" ") + "netmask" + White(" ") + ipAddress) + struct) option = Group(Literal("option") + name + value) host = Group(Combine("host" + White(" ") + name) + struct) ip_range = Group(Literal("range") + Combine(ipAddress + White(" ") + ipAddress)) shared_network = Group(Literal("shared-network") + name + struct) allow_deny = Group((Literal("allow") | Literal("deny")) + CharsNotIn(";").strip()) pool = Group(Literal("pool") + struct) struct << Group( LBRACE + ZeroOrMore( subnet | host | shared_network | ip_range | option | hw_eth | pool | allow_deny | name_value ) + RBRACE ) parser = Group( OneOrMore( subnet | host | shared_network | ip_range | option | hw_eth | pool | allow_deny | name_value ) ) parser.ignore(comment) parser.ignore(";") # 自定义后处理函数,把解析后的列表转为目标字典 def process_config(items): res = {} for item in items: key = item[0] val = item[1] if len(item) > 1 else item[0] # 处理option项 if key == "option": opt_key = item[1] opt_val = item[2] if "options" not in res: res["options"] = {} # 单元素列表就展开 if isinstance(opt_val, list) and len(opt_val) == 1: opt_val = opt_val[0] res["options"][opt_key] = opt_val continue # 处理shared-network项 if key == "shared-network": net_name = item[1] net_conf = process_config(item[2]) if "shared-networks" not in res: res["shared-networks"] = {} res["shared-networks"][net_name] = net_conf continue # 处理pool项 if key == "pool": pool_conf = process_config(val) if "pools" not in res: res["pools"] = [] res["pools"].append(pool_conf) continue # 处理allow/deny多值 if key in ("allow", "deny"): if key not in res: res[key] = [] res[key].append(val.strip()) continue # 处理嵌套结构 if isinstance(val, list) and all(isinstance(i, (list, str)) for i in val): val = process_config(val) # 普通值处理,重复的话转列表 if key in res: if not isinstance(res[key], list): res[key] = [res[key]] res[key].append(val) else: # 单元素列表展开 if isinstance(val, list) and len(val) == 1: val = val[0] res[key] = val return res # 测试用的dhcpd.conf样例数据 data = """ ddns-update-style none; log-facility local7; deny unknown-clients; deny booting; subnet 10.42.200.0 netmask 255.255.255.0 { max-lease-time 28800; option routers 10.42.200.1; option domain-name-servers 8.8.8.8, 8.8.4.4; range 10.42.200.10 10.42.200.200; } shared-network 224-29 { subnet 10.17.224.0 netmask 255.255.255.0 { option routers rtr-224.example.org; } subnet 10.0.29.0 netmask 255.255.255.0 { option routers rtr-29.example.org; } pool { allow members of "foo"; range 10.17.224.10 10.17.224.250; } pool { deny members of "foo"; range 10.0.29.10 10.0.29.230; } } """ try: raw_result = parser.parseString(data)[0] result = process_config(raw_result) except ParseException as pe: str_pe = str(pe) if "found end of text" in str_pe: pass else: raise pe pprint(result, sort_dicts=False)
运行上述代码输出的结构和预期完全一致,支持多deny/allow、多pool、option统一归集、shared-network归集的需求。
内容的提问来源于stack exchange,提问作者carloslockward
相关产品推荐
相关产品推荐

