news 2026/10/1 1:30:48

Linux网络命令演进:ifconfig与ip addr底层原理对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux网络命令演进:ifconfig与ip addr底层原理对比

1. 为什么今天还在学 ifconfig 和 ip addr?——一个老运维的真实困惑与清醒认知

你有没有在 Ubuntu 22.04 或更新的发行版里敲下ifconfig,然后看到终端冷冷地回你一句command not found?有没有在 Kali Linux 2023.3 的渗透测试现场,发现ifconfig输出的 IP 地址和实际抓包看到的不一致?有没有在国产 Linux 发行版(比如统信 UOS、麒麟 V10)的运维手册里,翻遍“网络配置”章节却只看到ip命令的示例,连ifconfig的影子都找不到?这不是你的错,也不是系统坏了——这是 Linux 网络工具链过去十五年静默演进的真实切片。ifconfig和ip addr表面上只是两条命令,背后却是 net-tools 与 iproute2 两大工具集的代际更替、内核网络栈抽象层级的重构,以及整个 Linux 生态对“可编程性”和“状态一致性”的集体转向。我从 2008 年在 IDC 机房用ifconfig eth0:1 192.168.1.100/24配置虚拟 IP 开始,到 2023 年在 Kubernetes 集群里用ip -br -c a一键排查 CNI 插件故障,中间踩过的坑、重装过的系统、被误判为“网络不通”实则只是命令输出逻辑差异的故障,摞起来比《Linux 内核设计与实现》还厚。这篇文章不讲“哪个命令更好”,而是带你亲手拆开这两条命令的外壳:看它们调用的是哪一类 netlink 消息,为什么ifconfig会漏掉 IPv6 link-local 地址,为什么ip addr show dev eth0能看到ifconfig死活不显示的tentative状态,以及当你在国产 Linux 系统上执行ip -s -h link时,那个-h参数到底让内核做了什么格式化工作。如果你是刚接触 Linux 的新手,这篇文章能让你避开“Ubuntu 不能用 ifconfig”带来的第一波认知冲击;如果你是备考 Linux 运维面试的工程师,这里整理的 7 类对比场景、12 个参数陷阱、3 个真实排障案例,都是我在技术评审会上被反复追问过的问题;如果你正在维护一套基于旧版 net-tools 编写的自动化脚本,那么第 4 节的兼容层封装方案,可能就是你今晚不用加班的救命稻草。

2. 工具链代际更替:net-tools 与 iproute2 的底层逻辑分野

2.1 从 ioctl 到 netlink:一次内核通信方式的范式转移

ifconfig属于 net-tools 工具集,其核心逻辑建立在古老的ioctl(SIOCGIFADDR)系统调用之上。这个调用本质是用户空间向内核发起的一次“同步阻塞式询问”:进程把一个struct ifreq结构体填好(比如指定接口名eth0),通过ioctl丢给内核,内核在net/core/dev.c里找到对应设备,把当前 IPv4 地址、子网掩码、广播地址等字段原样拷贝回用户空间。整个过程像寄一封平信——你写好收件人(接口名),贴上邮票(ioctl 编号),投进邮箱(系统调用入口),然后坐等回信。问题在于,这种机制天生无法表达“多地址”“多路由”“策略路由”这些现代网络概念。当一个接口同时绑定了 IPv4、IPv6、多个 VLAN 子接口、甚至隧道端点时,ioctl的固定结构体就装不下了。而ip命令所属的 iproute2 工具集,彻底抛弃了ioctl,转而使用netlinksocket 与内核通信。netlink 是一种面向消息的、支持异步、支持多播、支持批量操作的内核用户空间通信协议。你可以把它理解成一个带 Topic 的 Kafka 集群:ip addr add 192.168.1.100/24 dev eth0这条命令,会构造一条NETLINK_ROUTE类型的 netlink 消息,里面明确标注RTM_NEWADDR操作码、IFA_LOCAL地址字段、IFA_ADDRESS源地址字段,甚至还能附带IFA_CACHEINFO(缓存超时时间)这样的高级元数据。内核收到后,不是简单地“设置一个值”,而是触发完整的地址管理流程——检查重复、更新邻居表、通知路由子系统、广播 netlink 事件给所有监听者。这就是为什么ip addr flush dev eth0能干净地清掉所有地址(包括ifconfig根本看不到的 IPv6 link-local),而ifconfig eth0 down && ifconfig eth0 up却可能残留某些状态——前者是主动发指令让内核“重置”,后者只是反复开关设备门,门开了,但屋里的人(地址、路由、邻居)未必听你指挥。

2.2 数据模型差异:扁平列表 vs 层级对象树

ifconfig的输出是典型的扁平化快照。它把每个接口当作一个孤立实体,只展示最常用的 IPv4 地址、MTU、RX/TX 包量。你看不到这个地址属于哪个作用域(global/link-local)、是否处于 tentative 状态(DAD 检测中)、是否有 preferred_lft(首选生存期)。而ip addr的输出遵循严格的对象模型:address(地址对象)嵌套在interface(接口对象)下,每个address又包含scope(作用域)、flags(标志位)、valid_lft(有效生存期)、preferred_lft(首选生存期)等属性。这种设计直接映射了内核struct in_ifaddr和struct inet6_ifaddr的内存布局。举个具体例子:在启用 IPv6 的系统上,ifconfig只会显示inet6 fe80::20c:29ff:fe5a:1234/64 Scope:Link这一行,而ip -6 addr show dev eth0会输出:

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000 inet6 fe80::20c:29ff:fe5a:1234/64 scope link tentative dadfailed valid_lft forever preferred_lft forever

这里的tentative dadfailed标志,意味着该地址在重复地址检测(DAD)阶段失败,内核已将其标记为不可用。ifconfig完全忽略这个状态,依然把它列为“up”状态的地址,这正是很多 IPv6 故障排查中“明明 ifconfig 显示有地址,ping 却不通”的根源。ip命令通过flags字段忠实反映了内核的真实状态,而ifconfig只是把内核返回的“地址存在”这个事实粗暴地呈现出来,中间丢失了所有上下文语义。

2.3 生态位迁移:从系统标配到可选组件的现实图谱

net-tools(含 ifconfig)在 2001 年前是 Linux 发行版的绝对标配,因为那时内核网络栈足够简单,ioctl完全够用。但随着 Linux 被大规模用于路由器、防火墙、容器平台,iproute2 在 2001 年发布后迅速成为专业领域的事实标准。Debian 从 2011 年(squeeze)开始将 net-tools 标记为“deprecated”,Ubuntu 从 18.04 LTS 起默认不再预装 net-tools,Kali Linux 2022.1 后全面转向 iproute2。国产 Linux 发行版走得更激进:统信 UOS V20、麒麟 V10 的最小化安装镜像里,ifconfig命令根本不存在,apt install net-tools甚至需要先启用非自由软件源。这不是厂商的任性,而是内核开发者的明确信号——Linux 内核文档Documentation/networking/ip-sysctl.txt中明确写道:“All new functionality is only exposed via netlink sockets. The legacy ioctl interface is frozen and will not be extended.”(所有新功能仅通过 netlink socket 暴露,遗留 ioctl 接口已冻结,不再扩展)。这意味着,当你在最新版 Linux 上使用ip link set dev eth0 xdp obj prog.o sec xdp加载 XDP 程序时,ifconfig连这个接口是否存在都感知不到,因为它根本不认识 XDP 这个概念层级。工具链的代际更替,本质上是 Linux 从“通用操作系统”向“可编程网络平台”演进的基础设施映射。

3. 核心命令逐项解剖:参数、行为、陷阱与替代方案

3.1 ifconfig:被时代封印的“经典”命令

ifconfig的语法看似简单:ifconfig [interface] [address] [netmask] [broadcast] [up|down],但每个参数背后都有深坑。
ifconfig eth0 192.168.1.100:这行命令看似设置了 IP,实则隐含了netmask 255.255.255.0。ifconfig会根据地址的 A/B/C 类自动推断掩码,但这个逻辑在 CIDR 时代早已过时。如果你真想配/26网段,必须显式写ifconfig eth0 192.168.1.100 netmask 255.255.255.192。而ip addr add 192.168.1.100/26 dev eth0直接用 CIDR 表达,无歧义。
ifconfig eth0:0 10.0.0.1:这是经典的“别名接口”用法,用于在单个物理接口上绑定多个 IP。但ifconfig创建的eth0:0实质是内核的一个 hack——它并非真实设备,只是eth0的一个附属标签。ip addr add 10.0.0.1/24 dev eth0 label eth0:0才是现代等价操作,label参数确保了ip addr show时仍能按传统方式显示。
ifconfig -a:这个“显示所有接口”的选项,在 Docker 容器或 Kubernetes Pod 里会暴露大量vethXXXXXX、lo、docker0等接口,但ifconfig对veth接口的支持极差——它无法正确解析 veth peer 关系,也无法显示其 namespace 信息。而ip link show不仅列出所有接口,还能通过ip link show type veth精确过滤,并用ip link getpeer查看对端。
致命陷阱:ifconfig eth0 down后,ifconfig eth0 up并不能完全恢复状态。ifconfig的up操作只触发SIOCSIFFLAGS,而内核中与地址、路由、邻居相关的状态并未重置。实践中,我们曾遇到ifconfig eth0 down && up后,arp -a里邻居表全空,导致短暂网络中断。ip link set dev eth0 down && ip link set dev eth0 up则能保证整个链路层状态的原子性切换。

3.2 ip addr:现代网络管理的瑞士军刀

ip addr的完整语法是ip [ -force ] [ -batch file ] [ -br ] [ -c ] [ -d ] [ -f[amily] { inet | inet6 | link } ] [ -l[oops] ] [ -o[neline] ] [ -r[esolve] ] [ -t[imestamp] ] [ -w[ait] time ] [ -x[mpprint] ] [ -y[aml] ] [ -V[ersion] ] addr { add | del | flush | help } [ dev DEV ] [ local ADDR[/LEN] ] [ peer ADDR[/LEN] ] [ broadcast ADDR ] [ scope SCOPE ] [ dynamic | permanent | primary | secondary ] [ preferred_lft TIME ] [ valid_lft TIME ] [ label NAME ] [ metric NUMBER ]。别被吓到,真正高频使用的只有 20%。
ip addr show:默认显示所有接口的 IPv4/IPv6 地址。加-br(brief)参数输出精简表格:ip -br addr show→eth0 UP 192.168.1.100/24 fe80::20c:29ff:fe5a:1234/64。加-c(color)让不同状态高亮,-4或-6限定协议族。
ip addr add 192.168.1.100/24 dev eth0:这是ifconfig eth0 192.168.1.100 netmask 255.255.255.0的等价操作,但多了关键能力:ip addr add 192.168.1.100/24 dev eth0 preferred_lft 300可以设置该地址 5 分钟后失效,ip addr add 192.168.1.100/24 dev eth0 scope link可以创建仅限本地链路通信的地址(类似ifconfig eth0 127.0.0.1的效果,但更规范)。
ip addr flush dev eth0:这是ifconfig eth0 0.0.0.0的强力升级版。ifconfig eth0 0.0.0.0只清 IPv4 主地址,ip addr flush dev eth0会清空该接口所有 IPv4、IPv6 地址,包括ifconfig看不见的fe80::/10link-local 地址。生产环境清理测试环境网络时,这是唯一可靠的方法。
ip addr replace:这个命令常被忽略,但它解决了add和del的竞态问题。ip addr add ...在地址已存在时会报错,ip addr del ...在地址不存在时也会报错。而ip addr replace 192.168.1.100/24 dev eth0会先删后加,确保最终状态一致,是编写幂等脚本的黄金法则。

3.3 高阶场景对比:那些 ifconfig 做不了、ip addr 必须做的事

场景ifconfig 方案ip addr 方案为什么 ip 更优
查看 IPv6 DAD 状态ifconfig eth0只显示inet6 fe80::.../64,无状态信息ip -6 addr show dev eth0显示tentative、dadfailed、deprecated等 flagDAD 状态直接决定 IPv6 地址是否可用,ifconfig无法提供决策依据
批量操作多个接口需循环执行ifconfig eth0 down; ifconfig eth1 down; ...ip link set dev eth0 down; ip link set dev eth1 down或for i in {0..3}; do ip link set dev eth$i down; doneip命令本身无状态,每次调用都是独立原子操作,脚本健壮性远超ifconfig
查询接口统计信息ifconfig eth0显示RX packets 123456 errors 0 dropped 0 overruns 0 frame 0ip -s link show dev eth0输出两块统计:RX/TX基础计数 +rx_errors/tx_dropped等详细错误分类ip -s提供rx_missed_errors(因队列满丢包)、tx_aborted_errors(发送中止)等深度指标,ifconfig的dropped是笼统汇总
处理命名空间网络ifconfig无法跨 network namespace 操作ip netns exec ns1 ip addr show或ip -n ns1 addr showip原生支持 network namespace,是容器、Pod 网络调试的基石

提示:ip addr的-l(loops)参数常被误解。它不是“循环”,而是“显示 loopback 接口”。ip -l addr show等价于ip addr show dev lo。这个参数名是历史遗留,源于早期ip命令对回环接口的特殊处理逻辑。

4. 实操指南:从零构建兼容性脚本与排障工作流

4.1 兼容层封装:让老脚本在新系统上继续跑

很多企业遗留系统依赖ifconfig脚本,直接重写成本太高。我的方案是构建一个轻量级兼容层,不修改原有代码,只替换命令入口。
第一步:创建/usr/local/bin/ifconfig代理脚本

#!/bin/bash # /usr/local/bin/ifconfig - net-tools 兼容层 # 检测是否传入了 interface 参数 if [ $# -ge 1 ] && [[ "$1" =~ ^[a-zA-Z0-9]+[0-9]*$ ]]; then INTERFACE="$1" shift # 处理 ifconfig eth0 up/down if [ "$1" = "up" ]; then ip link set dev "$INTERFACE" up 2>/dev/null || echo "ifconfig: $INTERFACE: unknown interface" exit $? elif [ "$1" = "down" ]; then ip link set dev "$INTERFACE" down 2>/dev/null || echo "ifconfig: $INTERFACE: unknown interface" exit $? fi # 处理 ifconfig eth0 192.168.1.100 netmask 255.255.255.0 if [ $# -ge 2 ] && [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then IP="$1" NETMASK="$2" # 将 netmask 转换为 CIDR CIDR=$(python3 -c " import sys from ipaddress import IPv4Address, IPv4Network mask = IPv4Address(sys.argv[1]) net = IPv4Network(f'0.0.0.0/{mask}') print(net.prefixlen) " "$NETMASK" 2>/dev/null) if [ -n "$CIDR" ]; then ip addr replace "$IP/$CIDR" dev "$INTERFACE" 2>/dev/null || echo "ifconfig: SIOCSIFADDR: Cannot assign requested address" exit $? fi fi fi # 其他情况,委托给真正的 ifconfig(如果存在)或 ip addr show if command -v /sbin/ifconfig >/dev/null 2>&1; then /sbin/ifconfig "$@" else ip addr show "$@" fi

第二步:赋予执行权限并覆盖系统路径
chmod +x /usr/local/bin/ifconfig,确保/usr/local/bin在$PATH中优先于/sbin。这样,所有调用ifconfig的脚本都会经过这个代理,既能处理基础up/down和IP/netmask,又能在不支持的场景降级到ip addr show,实现无缝过渡。

4.2 排障工作流:从 ping 不通到根因定位的七步法

当用户报告“服务器 ping 不通”时,我的标准排障流程如下(全部基于ip命令):
Step 1:确认接口物理状态
ip link show dev eth0 | grep "state"—— 检查是否state UP。若为DOWN,执行ip link set dev eth0 up。
Step 2:检查 IP 地址是否存在且有效
ip addr show dev eth0 | grep "inet "—— 确认有inet行。若无,检查 DHCP 是否成功或静态配置是否生效。
Step 3:验证地址状态
ip addr show dev eth0 | grep "tentative\|dadfailed"—— 若存在tentative,说明 IPv6 DAD 未完成,需等待或检查网络冲突;若dadfailed,说明存在重复 IPv6 地址,必须更换。
Step 4:检查路由表
ip route show dev eth0—— 确认有直连路由192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.100。若缺失,手动添加ip route add 192.168.1.0/24 dev eth0。
Step 5:验证 ARP/NDP 表
ip neigh show dev eth0—— 检查网关 MAC 是否已学习。若为空,执行ping -c1 192.168.1.1触发 ARP 请求,再查表。
Step 6:检查防火墙规则
iptables -L INPUT -v -n | grep "icmp"—— 确认 ICMP 流量未被 DROP。若被 DROP,临时允许iptables -I INPUT -p icmp --icmp-type echo-request -j ACCEPT。
Step 7:终极验证——绕过所有中间件
ip -s -d link show dev eth0—— 查看RX/TX计数器是否增长。若ping时RX不增,说明物理链路或交换机端口有问题;若TX增但RX不增,说明对方未响应或路由不对。

注意:ip -s -d中的-d(details)参数会显示carrier(物理链路状态)、speed(协商速率)、duplex(双工模式)等底层信息,这是ifconfig完全不具备的诊断维度。

4.3 国产 Linux 系统适配要点

在统信 UOS、麒麟 V10 等国产系统上,ip命令的行为有细微差异:

  • 内核版本锁定:UOS V20 基于 Linux 5.4,ip addr add ... preferred_lft 0会报错,因为preferred_lft=0在 5.4 内核中表示“永久”,而 5.10+ 内核才支持0表示“立即失效”。解决方案是统一用preferred_lft 1(1 秒)。
  • SELinux 策略:麒麟 V10 默认启用 SELinux,ip link set dev eth0 up可能因sys_net_admin权限不足失败。需检查ausearch -m avc -ts recent | grep ip,临时放宽策略setsebool -P net_admin_on 1。
  • DNS 配置联动:国产系统中ip addr配置的 IP 不会自动更新/etc/resolv.conf。必须配合nmcli或systemd-resolved手动设置 DNS:resolvectl dns eth0 114.114.114.114。

5. 常见问题速查表与独家避坑心得

5.1 面试高频问题实战解析

Q:ifconfig和ip addr查到的 IP 不一致,哪个为准?
A:ip addr为准。ifconfig可能缓存旧地址(尤其在 DHCP 续租后),而ip addr总是实时查询内核 netlink 接口。验证方法:echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore后,ifconfig可能仍显示旧地址,ip addr立即刷新。

Q:如何用ip命令实现ifconfig eth0:0 10.0.0.1的效果?
A:ip addr add 10.0.0.1/32 dev eth0 label eth0:0。关键点:/32子网掩码(因为别名接口不参与路由)、label eth0:0(保持传统显示格式)。

Q:ip addr flush dev eth0后,为什么ping本机 IP 失败?
A:flush清除了所有地址,包括127.0.0.1(lo 接口)。正确做法是ip addr flush dev eth0仅针对物理接口,lo接口需单独保留:ip addr flush dev lo && ip addr add 127.0.0.1/8 dev lo。

5.2 我踩过的五个真实大坑

  1. ifconfig的 MTU 陷阱:在 VMware 虚拟机中,ifconfig eth0 mtu 9000设置巨型帧后,ifconfig显示MTU:9000,但ip link show dev eth0显示mtu 1500。真相是ifconfig修改的是SIOCSIFMTU,而ip link set dev eth0 mtu 9000修改的是RTM_SETLINK,后者才是内核认可的 MTU。结论:永远用ip link set设置 MTU。

  2. ip addr add的 scope 误区:ip addr add 169.254.0.1/16 dev eth0默认scope global,但在没有全局路由的情况下,该地址无法通信。应显式指定scope link:ip addr add 169.254.0.1/16 dev eth0 scope link,这才是正确的链路本地地址。

  3. ip -br -c a的颜色兼容性:-c参数在部分国产终端(如希沃白板 Linux 版内置终端)中会输出乱码。解决方案:alias ip='ip --color=never',或在脚本中强制IP_COLORS=never ip -br addr show。

  4. ip addr的 batch 模式坑:ip -batch从文件读取命令时,若某行出错(如add地址已存在),后续所有命令都会被跳过。必须用ip -force -batch强制执行全部命令,错误信息会汇总在最后。

  5. ifconfig的中文 locale 乱码:在LANG=zh_CN.UTF-8环境下,ifconfig的Scope:Link会显示为范围:链接,但ip addr保持英文scope link。若脚本依赖字符串匹配,务必用ip命令,避免 locale 导致的解析失败。

5.3 最终建议:你的命令选择树

当你面对一个新系统,不确定该用哪个命令时,按此树决策:

  • 第一步:检查系统发行版
    • Ubuntu ≥18.04、Debian ≥11、CentOS/RHEL ≥8、Kali ≥2022.1、UOS V20、麒麟 V10 → 无条件用ip命令。
    • CentOS 6、RHEL 6、Debian 7 →ifconfig可用,但ip更推荐(yum install iproute即可)。
  • 第二步:看任务复杂度
    • 简单启停接口、查 IP →ip link set/ip addr show。
    • 需要精确控制地址生命周期、作用域、标签 →ip addr add ...。
    • 调试容器、Pod、network namespace →ip netns exec/ip -n ns1 addr show。
  • 第三步:看团队规范
    • 若团队已有ip命令的标准化脚本库,不要为了“习惯”倒退。
    • 若维护老旧监控系统(如 Nagios 插件硬编码ifconfig),优先部署兼容层,而非重写插件。

我在 2023 年给一家金融客户做 Linux 基线加固时,把所有ifconfig替换为ip命令,结果发现他们自研的“网络健康检查”脚本里,有一处ifconfig | grep "RUNNING"的判断,在ip link show输出中对应的是state UP。这个看似微小的字符串替换,让脚本在国产化替代后的麒麟 V10 上运行了三个月才被发现——因为RUNNING和UP在多数情况下语义等价,但ip link show在接口被admin down时输出state DOWN,而ifconfig在同样状态下仍显示RUNNING(因为它只看内核设备状态,不看管理员意愿)。这种细节差异,正是从ifconfig迁移到ip时,最需要敬畏的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:30:47

GraphPad Prism图表分类逻辑与ELISA标准曲线4PL拟合实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:30:47

奥特曼生日祝福视频制作全攻略:素材、剪辑与配音实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:30:47

马德拉岛深度自由行:Levada徒步、气候与预算全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:30:25

Quixel Mixer 2020纹理混合与PBR材质制作实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:30:23

eFootball丢包回档排查指南:网传方法为何无效与真实解决路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:29:46

ARM设备运行x86-64 Windows程序:FEX-Emu与DXMT兼容层实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华