You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 11:27:04