news 2026/10/1 9:27:28

虚拟机命令行工具实战:VBoxManage、vmrun与virsh全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
虚拟机命令行工具实战:VBoxManage、vmrun与virsh全解析

不知道你有没有过这样的经历:明明只是想从宿主机往虚拟机里拷贝一份文件,结果图形界面点半天找不到共享文件夹;或者想在CI服务器上无头批量创建几台测试机,却只能对着Vmware Workstation的窗口发呆。我做了这么多年虚拟化相关的事情,从VirtualBox到KVM再到VMware,最后发现真正能一锤定音的,往往不是图形界面里那个按键,而是藏在安装目录深处的命令行工具。这个系列如今写到了第330篇,这篇文章就把我在虚拟机命令行工具上攒下来的经验一次讲透。


1. 虚拟机命令行工具全景与选型思路

1.1 为什么一定要让命令行接管虚拟化

我们先把“命令行”这件事的定位搞清楚。对一台物理机,命令行和图形界面是两种平行的操作方式;但对虚拟机来说,命令行往往不只是“另一种方式”,而是唯一可靠的方式。原因有三个:

第一,虚拟机服务多数跑在无显示器的服务器上,也就是headless环境。不管是KVM还是ESXi,远程管理的默认入口要么是SSH,要么是API,图形界面是后加的。想用GUI管理远程虚拟化平台,通常得额外开VNC或者装专门的客户端,而命令行天然就是远程的、带鉴权的、可审计的。

第二,虚拟机的生命周期管理极为重复。创建、配置、加盘、做快照、还原、销毁,这些动作一旦超过三五台机器,GUI的点选效率就急剧下降,而且人眼点选极容易漏参数。命令行工具能把这些动作变成参数化的命令,写成脚本后整套流程可以反复执行,结果完全一致。

第三,命令行工具的排错信息通常比GUI直白得多。GUI把错误弹窗做完后就没了,命令行工具直接在终端把错误码、日志路径、依赖关系打出来,顺着指引查下去,问题基本都能定位。

所以我一直有个观点:图形界面适合单台机器的临时操作,命令行适合批量、重复、自动化的管理场景。如果你的虚拟机数量大于等于三台,或者你打算把虚机融入CI流程,命令行这条路迟早要迈进来,而且早迈早省心。

1.2 两大阵营与一条额外主线

现在市面上的虚拟机命令行工具,大致能分成两大阵营,外加一条辅助主线,我整理成了一张表:

阵营核心工具适用场景最关键能力
VirtualBox 阵营VBoxManage桌面开发机、轻量测试、跨平台(Win/Linux/macOS)全套虚机生命周期管理、快照、网络拓扑
VMware 阵营vmrun、vmware-vdiskmanagerVMware Workstation / Player / ESXi 生态虚机启停、Guest内命令执行、磁盘维护
KVM/QEMU 阵营virsh、qemu-img、qemu-system-x86_64Linux 服务器、云计算底层批量定义虚机、磁盘格式转换、极客级控制
辅助主线guestmount、qemu-nbd、cloud-init镜像调整、离线注入文件、自动初始化不启动虚机直接改磁盘内容、批量初始化

为什么要把辅助主线单独拎出来?因为实际使用中,最费时间的往往不是“创建一台虚机”,而是“把已有的镜像改造成自己需要的形态”:改个账号、塞个SSH公钥、调整分区大小,这些事如果每次都启动虚机去操作,一两台还行,十台以上就会让人崩溃。guestmount这类离线工具就是干这个的,后面我会专门展开。

至于选型,我的建议很简单:如果你主力是Windows或者macOS,优先VirtualBox加VBoxManage,零成本且功能完整;如果你已经买了或习惯了VMware Workstation,那vmrun值得花半小时熟悉;如果你有Linux服务器,KVM阵营的virsh和qemu-img是绕不过去的核心。三套工具的命令风格不同,但设计思路高度一致,学通一套再上手另一套会非常快,这也是命令行工具的一个隐性好处——概念都是共通的。

2. 三个主力工具的拆解与实操要点

2.1 VBoxManage:VirtualBox 的瑞士军刀

先说VirtualBox的命令行老大VBoxManage。这个工具在安装VirtualBox时就会一起装上,Windows下在VirtualBox安装目录里,Linux下通常在/usr/bin/vboxmanage(注意大小写,和GUI的程序名VBoxManage不完全一致)。

我最常用的场景是纯命令行创建一台Linux虚机,核心命令是一个组合:

VBoxManage createvm --name "deb-test" --ostype Ubuntu_64 --register VBoxManage modifyvm "deb-test" --memory 2048 --cpus 2 --nic1 nat --graphicscontroller vmsvga VBoxManage createhd --filename ~/VirtualBox VMs/deb-test/deb-test.vdi --size 20480 VBoxManage storagectl "deb-test" --name "SATA" --add sata --controller IntelAhci VBoxManage storageattach "deb-test" --storagectl "SATA" --port 0 --device 0 --type hdd --medium ~/VirtualBox VMs/deb-test/deb-test.vdi VBoxManage modifyvm "deb-test" --boot1 dvd --nic1 nat VBoxManage startvm "deb-test"

这个写法的好处是每一步都是显式的:先登记虚机,再定内存CPU和网卡,再建虚拟磁盘,再挂到SATA控制器上,最后启动。全程不需要打开主界面,跑完就是一台可用的虚机。如果你想从安装ISO引导,加一句--medium /path/to/ubuntu.iso --type dvddrive到storageattach就行。

这里有几个必须注意的点:

createvm的--register参数容易漏。如果没加,虚机虽然创建了,但不会出现在VirtualBox的全局注册表里,GUI看不到,startvm也会报“找不到虚机”。补上VBoxManage registervm 路径.vbox也能救回来。

modifyvm修改网卡和内存时,虚机必须处于poweroff状态,否则会报Machine is already running。我在脚本里会先检查状态再修改,避免跑了一半报错。

磁盘控制器的类型影响操作系统安装的成功率。Windows虚机建议用SATA加IntelAhci,老系统用IDE兼容性更好;Linux虚机则没有那么多讲究。这块如果选错,安装阶段蓝屏或者找不到磁盘是最常见的结果。

快照操作也是脚本化的重头戏。创建还原点、回滚、删除旧快照,三个命令就够用:

VBoxManage snapshot "deb-test" take "clean-install" --description "安装完成未配置" VBoxManage snapshot "deb-test" restore "clean-install" VBoxManage snapshot "deb-test" delete "clean-install"

我实测下来,快照对开发和测试的帮助极大。特别是拿到一个云的镜像或者公开的box之后,先打一个“原始状态”快照,再随便折腾系统配置,搞坏了直接restore,几秒钟回到干净状态,比重新装一遍系统省半小时都不止。

2.2 vmrun:VMware 的隐藏命令行

接着看VMware阵营。很多人装了VMware Workstation,却不知道安装目录里藏着一个功能很完整的命令行工具vmrun。在Windows上它位于VMware安装目录,比如C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe,Linux版则有独立的vmrun二进制。

vmrun的主打能力是虚机控制和Guest内命令执行。常用的有这几条:

vmrun -T ws start "D:\VMs\win11\win11.vmx" nogui vmrun -T ws stop "D:\VMs\win11\win11.vmx" soft vmrun -T ws snapshot "D:\VMs\win11\win11.vmx" clean-state vmrun -T ws list vmrun -T ws runProgramInGuest "D:\VMs\win11\win11.vmx" -interactive "C:\Windows\System32\cmd.exe" /c "echo ok" vmrun -T ws copyFileFromGuestToHost "D:\VMs\win11\win11.vmx" "/root/test.txt" "C:\tmp\test.txt"

-T ws这个参数表示操作对象是Workstation本地虚机,ESXi上则是-T esxi加服务器地址账号密码。nogui参数尤其重要,它在不弹出虚机窗口的情况下启动虚机,是无人值守和自动化测试的标配。

runProgramInGuest和copyFileFromGuestToHost这两条命令威力很大,因为它们能在虚机内部执行命令,相当于把SSH的能力包了一层,凌驾于系统状态之上。但有个前提:Guest内必须安装并运行VMware Tools,否则vmrun根本连不进去。这是它和SSH最本质的差别,也是被坑得最多的地方。

磁盘维护方面,vmware-vdiskmanager负责vmdk的扩容、碎片整理和格式转换,比如把单文件vmdk转成拆分的2GB小文件方便拷贝,我曾经把一个几十GB的vmdk拆成小文件传到移动硬盘,比单文件复制快且稳妥得多。命令长这样:

vmware-vdiskmanager -r "D:\VMs\test\disk.vmdk" -t 1 "D:\VMs\test\disk-split.vmdk"

-t 0表示单文件、-t 1表示拆分为2GB文件、-t 2表示预分配空间。这个参数在日常传输和归档时经常用到。

2.3 virsh 与 qemu-img:Linux 服务器上的王牌组合

如果你手头有一台Linux服务器,且CPU支持虚拟化,那KVM/QEMU阵营的virsh和qemu-img就是不二之选了。virsh是libvirt的Shell前端,qemu-img是QEMU的磁盘管理工具。这对组合对服务器的管理能力,是前面两者比不上的。

virsh的日常操作,我压成几条:

virsh list --all # 列出所有虚机及状态 virsh dominfo myvm # 查看虚机CPU内存信息 virsh start myvm # 启动已定义的虚机 virsh shutdown myvm # 优雅关机(需要Guest内ACPI支持) virsh destroy myvm # 强制断电 virsh define /etc/libvirt/qemu/myvm.xml # 从XML定义虚机 virsh undefine myvm --remove-all-storage # 删除虚机及磁盘 virsh domifaddr myvm # 获取虚机IP地址

最让我舒爽的是virsh domifaddr,它会直接告诉你这台虚机的IP地址,不用登录虚机,不用猜网段。对于跑在NAT网络里的虚机来说,这条命令是日常救命的。

qemu-img则是磁盘格式界的万能转换器。最常见的需求是把一个raw磁盘转成qcow2,或者把qcow2转成vmdk,命令很简单:

qemu-img convert -f raw -O qcow2 disk.raw disk.qcow2 qemu-img convert -f qcow2 -O vmdk disk.qcow2 disk.vmdk qemu-img info disk.qcow2 qemu-img check disk.qcow2

转换是最占用时间的环节之一,因为它是实打实的全盘复制。我有一次把一个200GB的raw转成qcow2,跑了一整晚。后来学聪明了,先用qemu-img info看实际占用大小,再决定是否压缩转换,虚拟空间占用不等于实际文件大小,这个认知能省不少事。

qcow2格式本身也推荐优先使用,因为它的文件大小是“按需增长”的,新建时哪怕虚机显示有20GB磁盘,实际文件可能只有几百KB;它还天然支持快照和写时复制,对个人玩虚拟化和做实验来说非常友好。唯一的缺点是性能略低于raw,但对开发测试场景来说完全够用。


3. 一整套可复用的自动化工作流

3.1 场景:批量创建同一底座的 Linux 测试虚机

前面把工具讲清楚了,现在就串起一条完整的工作流,展示怎么把命令行工具组合起来,解决一个真实问题:我有同一个ubuntu云镜像,想批量创建5台测试虚机,每台配置不同的IP,还要预置各自的工作目录和SSH公钥。纯手工操作,这事得忙一下午;用命令行脚本,十分钟搞定且不会出错。

第一步是准备基础镜像。现在各大发行版都提供cloud image,体积小,专门为虚机设计。拿到手之后,用qemu-img转成自己需要的格式并改名:

wget https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img qemu-img convert -f qcow2 -O qcow2 ubuntu-22.04-server-cloudimg-amd64.img base.qcow2 qemu-img resize base.qcow2 50G

qemu-img resize只改虚拟磁盘的容量标记,不会动里面的文件系统,所以这一步非常快。你可能会问,都resize到50G了,文件系统还是20G怎么办?这就需要在虚机第一次启动时自动扩容,办法在下一步的cloud-init里。

第二步是编写cloud-init配置文件,让它第一次启动时就把密码、SSH公钥和磁盘扩容一并搞定。这个配置挂在虚机里作为seed盘使用:

# user-data.yaml #cloud-config users: - name: dev sudo: ALL=(ALL) NOPASSWD:ALL shell: /bin/bash ssh-authorized-keys: - ssh-rsa AAAAB3NzaC1yc2E... your-key password: "temp-pass-123" chpasswd: expire: false growpart: mode: auto devices: ['/'] resize_rootfs: true

growpart和resize_rootfs这两个字段就是负责自动扩容的,首次启动时Cloud-Init会自动把根分区扩展到整个磁盘,不需要人工介入。这套机制比装完系统再手动改分区表省心太多了。

第三步就是把seed盘制作出来并启动虚机。制作seed盘可以用cloud-localds工具,也可以用genisoimage手动做,我习惯用最后一种方式,更直观:

cloud-localds seed.iso user-data.yaml qemu-system-x86_64 \ -name test-01 -m 2048 -smp 2 \ -drive file=base.qcow2,if=virtio \ -drive file=seed.iso,media=cdrom \ -netdev user,id=n1,hostfwd=tcp::2221-:22 \ -device virtio-net-pci,netdev=n1 \ -nographic

这里我用hostfwd把宿主机的2221端口映射到虚机的22端口,这样宿主机直接ssh -p 2221 dev@localhost就能登录,不受虚机IP变化影响,特别适合批量测试场景。如果想同时跑5台,就写一个循环脚本,动态生成端口号、机器名和seed卷,每台虚机彼此隔离,完全互不干扰:

for i in 1 2 3 4 5; do PORT=$((2220 + i)) cloud-localds "seed-$i.iso" <(sed "s/test-01/test-0$i/" user-data.yaml) qemu-system-x86_64 -name "test-0$i" -m 2048 -smp 2 \ -drive file="base-$i.qcow2",if=virtio \ -drive file="seed-$i.iso",media=cdrom \ -netdev user,id=n1,hostfwd=tcp:$PORT-:22 \ -device virtio-net-pci,netdev=n1 -nographic -daemonize done

这里用-daemonize把虚机放到后台跑,脚本结束不会卡住。批量创建之后,立刻用ss -tlnp | grep 222验证端口监听情况,比逐个打开虚机界面确认状态痛快得多。

3.2 实操心法:命令行塞进去的文件,比你想象得更多

上面这套流程看似只处理了Linux云镜像,但核心思想能复制到各种场景——通过seed盘、cloud-init或者离线工具,把“配置”而不是“操作”注入虚机。这是命令行自动化最值钱的地方:你在启动前就知道了这台机器的密码、IP、软件清单,而不是像GUI安装那样坐在屏幕前一页一页点Next。

我个人还有一个很喜欢的用法,就是离线注入文件。不启动虚机,直接给没开机的磁盘塞SSH公钥、改网络配置、修启动脚本,用guestmount就能做到:

sudo guestmount -a disk.qcow2 -i /mnt/fix sudo mkdir -p /mnt/fix/root/.ssh sudo cp id_rsa.pub /mnt/fix/root/.ssh/authorized_keys sudo guestunmount /mnt/fix

guestmount会把qcow2里的文件系统挂载到宿主机目录,改完后卸载即可。这对于批量修改多台虚机和管理员密码、追加公钥、临时修补配置文件来说,几乎就是魔法。还有个姊妹工具guestfish可以在不挂载的情况下直接对磁盘里的文件做增删改查,适合脚本化操作。

不过这里要泼盆冷水:guestmount操作的是离线磁盘,虚机必须处于关机状态。如果你在虚机运行中强行挂载它的磁盘,文件系统会可能损坏,数据一致性全没了。这是个血的教训——离线工具一切操作以“虚机没开”为前提,否则出了事别怪工具。

4. 常见问题与避坑实录

4.1 高频故障速查表

虚拟机的故障种类再多,列成表格也没几条是真正刁钻的。下面是我这些年实践里高频踩中且命令行环境下最容易处理的几类:

故障现象核心原因一句解决办法
Windows虚机启动就蓝屏磁盘控制器类型不符换SATA/IDE控制器重新挂载系统盘
虚机黑屏进不去桌面显卡驱动或内核参数问题加nomodeset进GRUB内核参数,或换vmsvga图形控制器
宿主机无法复制内容到Guest未装增强工具/Tools安装Guest Additions或VMware Tools后重启
虚机启动后宿主机自动关机重启宿主电源选项或硬件加速异常检查BIOS中的Intel VT-x是否开启,关闭Windows快速启动
复制粘贴的VB虚机无法打开磁盘UUID冲突用VBoxManage internalcommands sethduuid换新UUID
虚机与宿主机网络不通网络模式选择不当NAT对外访问、桥接做同级网络、host-only做隔离网络
磁盘空间不够但系统盘只占一点分区表未扩到虚拟磁盘末尾用growpart或GParted扩容分区
virsh启动报权限不够用户不在libvirt组sudo usermod -aG libvirt $USER后重新登录

这里面我但凡再啰嗦几句。——蓝屏问题。Windows虚机最常见的启动蓝屏,七成情况下不是系统坏了,而是你把磁盘从IDE模式换成了SATA,或者反之。操作系统在安装时加载的磁盘驱动是固定死的,换了控制器,它找不到盘就蓝。命令行处理办法是启动虚机时加参数确认控制器类型,或者挂载一个带驱动引导盘进去救,但最省事的还是不要随便改控制器。

——复制粘贴失败问题。这里面有个容易误判的点:宿主机界面里菜单是显示着“安装增强工具”,但如果你用的是命令行启动的虚机,VirtualBox的自动挂载可能不会生效,你复制进去的安装包加载不了。处理方式很简单,命令行里手工挂载VBoxGuestAdditions.iso,然后进系统执行安装脚本,强化安装完成后重启一次,剪贴板和拖拽文件就都通了。

4.2 命令行专属的几个隐蔽坑

除了虚机本身的故障,命令行操作还会遇到几类GUI里根本不存在的坑,我必须单独列一节。

一是路径白名单和空格问题。Windows下虚机路径经常带着空格,比如C:\Users\my name\VirtualBox VMs\test.vdi,命令行直接传参按空格会被拆成多个参数,在脚本里不加引号必翻车。我后来统一做法:所有虚机路径用引号包起来,并在脚本开头做断言检查,文件存在再继续,不存在就报错退出。

二是工具版本与输出格式不稳定的问题。VBoxManage和vmrun的某些旧版本输出提示语和现在的版本差异不小,写脚本做断言的时候,不要用关键字搜输出。我实测发现最稳妥的是用退出码判断命令成功与否,而不是试图解析自然语言输出。比如:

VBoxManage list vms >/dev/null 2>&1 if [ $? -eq 0 ]; then echo "vboxmanage可用" fi

三是磁盘路径被占用导致的操作顺序问题。命令行创建虚机、挂载磁盘、启动虚机这三个步骤如果乱序做,经常会报“磁盘被锁定”之类的话。正确顺序永远是createhd先建好磁盘,storageattach再挂载,最后startvm启动。如果顺序反了,磁盘文件会被锁定,你得先停掉虚机才能解开锁。

四是虚拟机内部的时间和时区问题。命令行创建虚机时很难注意到这个,但系统日志、证书校验都会因为时区错误而莫名其妙地失败。我在seed配置里固定加入timezone: Asia/Shanghai和ntp: enabled两个字段,避免后续排错时怀疑人生。

五是镜像格式与版本不匹配问题。很多人从网上下了一个vmdk或者vdi,拿去VirtualBox里导入失败,或者导入成功但启动就崩。原因往往是镜像在VMware里做成,磁盘尾部有额外的元数据。命令行的解决办法是统一转一遍格式再导入,比如用qemu-img convert将vmdk转成vdi。虽然多花点时间,但能抹平绝大多数兼容性问题。

4.3 把虚拟机转成 U 盘:从一个热搜说起

搜虚拟机相关热词时,我注意到一个高频词是“diskgenius转虚拟机为U盘”,这说明很多人都想把虚拟机的磁盘做成一个可启动的U盘。这个需求的实质是:把虚拟磁盘中的系统装进实体U盘。命令行方案其实比许多人想得简单,做法可以拆成两步。

第一步,用dd把虚拟磁盘的raw格式镜像写入U盘。如果是qcow2,先转成raw:

qemu-img convert -f qcow2 -O raw disk.qcow2 disk.raw sudo dd if=disk.raw of=/dev/sdb bs=4M status=progress conv=fsync

第二步,用分区工具调整U盘分区表。因为虚拟机镜像里如果既有EFI分区又有root分区,直接dd到U盘之后,U盘的分区布局会和镜像一致,多数情况下能直接引导。只有一些厂商的U盘主控兼容性不好,才需要DiskGenius把分区转成可引导格式、重建主引导记录。

我亲身做过一次,把一台ArchLinux虚机拷到U盘里,插到别的电脑上启动了,效果非常有意思。当然,受限于U盘的主控和接口速度,运行体验肯定不如NVMe硬盘,但作为“口袋里的开发环境”或“救援盘”来说已经足够实用。这个操作也再次验证了命令行工具的核心逻辑:磁盘格式只是头部的元数据,真正的数据是“一块一块的扇区”,只要能正确转换和写入,虚拟和物理之间的界限其实很模糊。


聊聊我个人的习惯吧。我始终把命令行工具当作虚拟化操作的主干道,图形界面留给那些需要直观观察的场景,比如调分辨率、看显示效果。越是复杂的批量操作,越信任命令行;越是排错困难的局面,越依赖命令行给出的底层线索。围绕虚拟机做自动化这件事,最值得花时间的不是记住某个命令的参数,而是培养“把一次手工操作提炼成可重复脚本”的意识。当你发现自己在图形界面反复点同一个操作时,就该停一下,问问这条路能不能用命令行跑一遍。能,就立刻去写。这几十分钟的投入,会在之后的每一次重复操作里连本带利地还回来。

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

虚幻引擎5.9渲染通道集成生成式AI图像与视频模型实战

/* 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 9:26:28

固定资产管理系统需求说明书拆解:从需求分析到数据建模实战

简介&#xff1a;最新版某某公司固定资产管理系统用户需求说明书&#xff0c;面向企业信息化部门、软件需求分析师及资产管理人员&#xff0c;用于明确系统功能范围与业务流程。文档系统梳理了资产全生命周期管理需求&#xff0c;涵盖资产登记、条形码/RFID集成、采购入库、分配…

作者头像 李华
网站建设 2026/10/1 9:26:17

从入门到维护:开源项目 README 编写规范与实操指南

项目代码写得再漂亮&#xff0c;如果 README 写得稀烂&#xff0c;这个项目的曝光和采用率都会大打折扣。我见过不少开发者把绝大多数精力放在代码重构、测试覆盖、CI 配置上&#xff0c;却把 README 当成最后随手补上的“作业”&#xff0c;结果项目明明技术很扎实&#xff0c…

作者头像 李华
网站建设 2026/10/1 9:26:10

零基础CTF入门指南:从环境搭建到拿下第一个flag的三个月路线

先说结论&#xff1a;CTF 不是只有天才才能玩的竞赛&#xff0c;它更像是一场“拆解游戏”的思维训练。哪怕你现在连 HTTP 协议是什么都不知道&#xff0c;只要愿意花三个月时间照着路线走&#xff0c;也能在 ctf练习网站 上拿到自己的第一个 flag。这个教程不给你灌鸡汤&#…

作者头像 李华
网站建设 2026/10/1 9:24: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 9:24:40

KANO模型实战指南:识别用户真实需求优先级

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

作者头像 李华