硬盘分区这件事,说大不大,说小也不小。我见过太多人拿到一块新盘,第一反应是打开图形化工具点几下,分区建好了,用着也没毛病。但一旦遇到批量部署、自动化脚本、或者服务器上没有图形界面的场景,图形工具就彻底歇菜了。这时候sfdisk就是那个能救命的东西——它是 util-linux 套件里的分区表编辑器,纯命令行操作,支持脚本化输入,能在一行命令里完成整块磁盘的分区布局。这篇内容适合两类人看:一类是刚接触 Linux 磁盘管理、想搞明白分区表到底怎么回事的新手;另一类是天天跟服务器打交道、需要把分区操作写进自动化流程的老手。我会从分区表的基本概念讲起,把sfdisk的常用操作、脚本模式、输出解析、踩坑经验全部摊开说,每个参数都解释清楚为什么这么用,每个步骤都给出可以直接复现的命令。
1. 先搞清楚 sfdisk 到底在操作什么
1.1 分区表:磁盘上的"目录页"
很多人用sfdisk的时候心里没底,根本原因是没想明白它改的到底是什么东西。一块裸盘刚插上机器,上面什么都没有,操作系统要在这块盘上划分出若干个逻辑区域,每个区域可以独立格式化、独立挂载,这就需要一张"目录页"来记录每个区域的起止位置、类型、大小等信息,这张目录页就是分区表。
分区表存放在磁盘最开头的几个扇区里,不占用你实际可用的存储空间。目前主流的分区表格式有两种:MBR(Master Boot Record)和GPT(GUID Partition Table)。MBR 是老格式,最大只支持 2TB 的磁盘,最多四个主分区;GPT 是 newer 的标准,支持超大容量磁盘,分区数量理论上没有硬性上限(实际受操作系统限制)。sfdisk对这两种格式都支持,而且能自动识别磁盘上现有的是哪种格式。
你可以把分区表想象成一本书的目录:目录本身不包含正文内容,但它决定了正文从第几页开始、到第几页结束、每一章叫什么名字。sfdisk就是那个帮你重写目录的工具——它不碰正文(数据),只改目录(分区表)。这也解释了为什么分区操作有风险:目录改错了,正文就找不到了。
1.2 sfdisk 与 fdisk、parted 的分工差异
Linux 下分区工具不止一个,常见的有fdisk、parted、gdisk,还有本文的主角sfdisk。它们之间的定位差异挺明显的,选错了工具会多走很多弯路。
fdisk是交互式的,你敲一个命令它问你一个问题,适合手动操作单块磁盘。parted功能更全面,支持调整分区大小、支持 GPT 和 MBR 互转,但它的交互模式对脚本化不太友好。gdisk专门针对 GPT 设计,功能强但只认 GPT。sfdisk的核心优势在于脚本化:它可以从标准输入读取一整套分区定义,一次性写入,不需要任何交互。这个特性在批量部署、自动化装机、CI 环境里非常关键。
| 工具 | 交互方式 | 脚本友好度 | MBR 支持 | GPT 支持 | 典型场景 |
|---|---|---|---|---|---|
| fdisk | 交互式菜单 | 低 | 是 | 是(较新版本) | 手动单盘操作 |
| parted | 交互式+命令行 | 中 | 是 | 是 | 分区调整、格式转换 |
| gdisk | 交互式菜单 | 低 | 否 | 是 | 纯 GPT 环境 |
| sfdisk | 命令行+脚本输入 | 高 | 是 | 是 | 批量部署、自动化 |
我个人的习惯是:手动调一块盘用fdisk或parted更顺手,但只要涉及脚本、批量、或者需要精确复现分区布局,一律用sfdisk。它的输出格式本身就是一种可复用的"分区配方",导出一块盘的分区表,改改就能套到另一块盘上,这个能力在实际运维里太实用了。
1.3 操作前的必备认知:设备名与权限
在动手之前,有两件事必须先确认清楚,否则轻则命令报错,重则写错盘导致数据丢失。
第一是设备名。Linux 下磁盘设备通常挂在/dev/目录下,SATA/SAS 盘一般是/dev/sda、/dev/sdb这种命名,NVMe 盘是/dev/nvme0n1、/dev/nvme1n1,虚拟化环境里可能是/dev/vda、/dev/xvda。分区则是在设备名后面加数字,比如/dev/sda1、/dev/nvme0n1p1(注意 NVMe 盘的分区名中间有个p)。用lsblk命令可以一眼看清当前系统所有块设备及其分区结构,这是操作前必跑的一条命令。
第二是权限。分区表操作直接写磁盘的原始扇区,普通用户没有这个权限,必须用root或者通过sudo提权。如果你在容器里操作,还要确认容器是否有对应的设备访问权限,很多容器默认不挂载块设备,sfdisk会报"找不到设备"的错误。
提示:执行任何写操作之前,先用
lsblk和sfdisk -l /dev/xxx确认目标设备,养成"先看后写"的习惯。写错盘的代价往往是整块数据不可恢复。
2. 查看现有分区布局:读懂 sfdisk 的输出
2.1 用 -l 参数列出分区表
sfdisk最基础也最常用的功能就是查看分区表,命令格式很简单:
sudo sfdisk -l /dev/sda这条命令会输出指定磁盘的分区表信息。如果不带设备名直接跑sfdisk -l,它会列出系统里所有可识别的磁盘。输出内容大致长这样:
Disk /dev/sda: 465.76 GiB, 500107862016 bytes, 976773168 sectors Disk model: ST500DM002-1BD14 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes I/O size (minimum/optimal): 4096 bytes / 4096 bytes Disklabel type: dos Disk identifier: 0x000a1b2c Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 526335 524288 256M 83 Linux /dev/sda2 526336 976773119 976246784 465.5G 83 Linux这段输出里每一行都有信息量,我逐个拆解。第一行给出磁盘总容量,单位是 GiB,同时给出精确的字节数和扇区数。Disk model是磁盘型号,排查硬件问题时有用。Units说明后续数字的单位是扇区,每个扇区 512 字节。Sector size分逻辑和物理两个值,物理扇区是 4096 字节说明这是一块 4K 盘,分区对齐时要特别注意。Disklabel type: dos表示当前是 MBR 分区表(dos 是 MBR 的另一种叫法),如果是 GPT 会显示gpt。
下面的表格是真正的分区列表。Device是分区设备名,Boot列的*表示该分区带启动标志,Start和End是起止扇区号,Sectors是分区占用的扇区总数,Size是换算后的人类可读大小,Id是分区类型代码(83 代表 Linux 普通分区,82 代表 swap,ef 代表 EFI 系统分区等),Type是类型名称。
2.2 扇区号与容量的换算逻辑
很多人看分区表输出时对Start和End这两个扇区号没概念,觉得跟自己没关系。实际上这两个数字是分区操作里最核心的参数,理解它们能帮你避开对齐问题。
扇区号乘以扇区大小就是字节偏移。以上面的输出为例,/dev/sda1从第 2048 扇区开始,2048 × 512 = 1048576 字节,也就是 1MiB 的位置。为什么从 2048 开始而不是从 0 开始?因为 0 到 2047 这 1MiB 空间留给了分区表和引导加载程序。现代磁盘(尤其是 SSD 和 4K 物理扇区的盘)要求分区起始位置按 1MiB 对齐,2048 扇区正好是 1MiB,这是行业惯例。
分区大小也可以用扇区数反推:Sectors列的值乘以 512 就是字节数。/dev/sda1有 524288 个扇区,524288 × 512 = 268435456 字节 = 256MiB,跟Size列显示的 256M 一致。你自己用sfdisk建分区时,如果直接指定扇区号,就要保证起始扇区是 2048 的整数倍(对 512 字节逻辑扇区的盘而言),这样才能保证对齐。
注意:4K 物理扇区的磁盘上,对齐要求更严格。虽然逻辑扇区仍是 512 字节,但物理写入以 4096 字节为单位,分区起始位置如果不是 4096 的整数倍,会导致跨物理扇区读写,性能明显下降。用
sfdisk时尽量用size=加单位的方式指定大小,让它自己算对齐,比手动填扇区号稳妥。
2.3 用 -d 导出可复用的分区配方
sfdisk -d是另一个高频命令,它把分区表导出成一种可以直接喂回sfdisk的格式:
sudo sfdisk -d /dev/sda输出类似这样:
label: dos label-id: 0x000a1b2c device: /dev/sda unit: sectors /dev/sda1 : start= 2048, size= 524288, type=83, bootable /dev/sda2 : start= 526336, size= 976246784, type=83这个格式就是sfdisk的"脚本语言"。label: dos指定分区表类型,unit: sectors说明后面的数字单位是扇区,每一行定义一个分区,start、size、type三个字段是核心,bootable标记启动分区。
这个导出的价值在于可复用。你可以把一块盘的分区布局导出成文件,稍作修改后直接应用到另一块盘上,实现分区布局的精确复制。批量部署几十台机器时,这个能力能省掉大量重复劳动。我通常的做法是:先在一台机器上手动分好一块"样板盘",用sfdisk -d导出配方存进配置仓库,装机脚本里直接sfdisk /dev/sdX < recipe.txt一把梭。
3. 动手建分区:从交互到脚本的完整路径
3.1 交互模式下逐条录入分区
sfdisk也支持交互模式,直接对目标磁盘执行:
sudo sfdisk /dev/sdb它会提示你输入分区定义,格式跟-d导出的格式一致。你可以一行一行敲,每行定义一个分区,敲完按Ctrl+D结束输入,sfdisk会先做语法检查,确认无误后写入磁盘。
交互模式适合临时调一块盘、边想边改的场景。但说实话,我很少用它——既然要用sfdisk,多半是为了脚本化,交互模式反而绕远了。真正好用的是下面这种管道输入的方式。
3.2 管道输入:一条命令完成整盘分区
把分区定义写成一个文本,通过管道喂给sfdisk,这是最常用的脚本化姿势:
cat <<EOF | sudo sfdisk /dev/sdb label: gpt unit: sectors start=2048, size=2097152, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B start=2099200, size=20971520, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 start=23070720, size=, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 EOF这段脚本建了三个分区:第一个是 EFI 系统分区(类型 GUID 对应 EFI),大小 2097152 扇区即 1GiB;第二个是 Linux 文件系统分区,大小 20971520 扇区即 10GiB;第三个也是 Linux 分区,size=留空表示占用剩余全部空间。
这里有几个细节值得说。label: gpt明确指定用 GPT 分区表,如果不写这一行,sfdisk会沿用磁盘上现有的分区表类型,新盘则默认用 dos(MBR)。unit: sectors声明单位,你也可以改成unit: MiB直接用兆字节为单位,省去换算。type=后面跟的是分区类型,MBR 用两位十六进制代码(如 83、82、ef),GPT 用 GUID,两种格式的类型标识方式完全不同,混用会报错。
第三个分区的size=留空是个实用技巧,表示"从 start 到磁盘末尾",这样不用手动计算剩余空间,换一块容量不同的盘也能直接套用同一份配方。
3.3 用 --append 追加分区而不破坏现有布局
有时候磁盘上已经有分区了,你只想在后面追加一个新分区,不想动现有的。这时候用--append参数:
echo 'start=, size=10GiB, type=83' | sudo sfdisk --append /dev/sdb--append的作用是保留现有分区表,把新分区追加到末尾。start=留空表示让sfdisk自动找下一个可用位置,size=10GiB指定新分区大小。这个参数在扩容场景里很常用——比如一块盘前面已经有系统分区,你想在后面加一块数据分区,用--append就不用重新规划整个布局。
提示:
--append只对 MBR 和 GPT 都有效,但 GPT 有备份分区表存放在磁盘末尾,追加分区时sfdisk会自动更新备份表,不需要手动干预。如果你用其他工具改过 GPT 分区表而没更新备份,sfdisk可能会提示备份表不一致,这时候用sfdisk --verify检查一下。
3.4 删除分区与清空分区表
删除单个分区用--delete参数,后面跟分区号:
sudo sfdisk --delete /dev/sdb 2这条命令删除/dev/sdb上的第 2 个分区。注意分区号是逻辑编号,不是设备名后缀,删除后后面的分区号不会自动重排(MBR 下主分区号固定,GPT 下分区号也保持稳定)。
要彻底清空整块盘的分区表,用--delete不带分区号,或者直接写一个空的分区定义:
sudo sfdisk --delete /dev/sdb这条命令会把整块盘的分区表抹掉,所有分区信息消失。执行前务必确认盘选对了,这个操作没有回收站。
4. 脚本化实战:把分区布局变成可复现的配方
4.1 分区配方的字段全解
要把sfdisk用出效率,得把它的脚本字段吃透。一份完整的分区定义支持以下字段:
| 字段 | 含义 | 是否必填 | 示例 |
|---|---|---|---|
| label | 分区表类型 | 否(默认沿用或 dos) | label: gpt |
| label-id | 分区表标识符 | 否 | label-id: 0x12345678 |
| unit | 数值单位 | 否(默认 sectors) | unit: MiB |
| start | 起始位置 | 是(可留空自动算) | start=2048 |
| size | 分区大小 | 是(可留空表示到末尾) | size=10GiB |
| type | 分区类型 | 否(默认 83/Linux) | type=83 |
| bootable | 启动标志 | 否 | bootable |
| name | 分区名称(GPT) | 否 | name="root" |
| uuid | 分区 UUID(GPT) | 否 | uuid=xxxx |
start和size都支持带单位,比如start=1MiB、size=512MiB,sfdisk会自动换算成扇区并对齐。用单位比裸写扇区号可读性好得多,也不容易算错。name字段只在 GPT 下有效,给分区起个名字,lsblk和/dev/disk/by-partlabel/下都能看到,排查问题时很方便。
4.2 一份可直接套用的多分区配方
下面这份配方是我在虚拟机环境里常用的布局,适合做通用服务器的基础盘:
cat <<EOF | sudo sfdisk /dev/vdb label: gpt unit: MiB start=1, size=512, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI" start=513, size=2048, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="boot" start=2561, size=8192, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="root" start=10753, size=4096, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name="swap" start=14849, size=, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="data" EOF这份配方用 MiB 作单位,可读性比扇区号好很多。第一个分区从 1MiB 开始(跳过分区表占用的空间),512MiB 给 EFI;第二个 2GiB 给 boot;第三个 8GiB 给根分区;第四个 4GiB 给 swap(类型 GUID 对应 Linux swap);最后一个占满剩余空间做数据盘。
每个start都是前一个分区的start + size,手动算容易出错,但sfdisk其实允许start留空自动接续。更省心的写法是把所有start都留空,只写size,sfdisk会按顺序自动排列:
cat <<EOF | sudo sfdisk /dev/vdb label: gpt unit: MiB size=512, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, name="EFI" size=2048, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="boot" size=8192, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="root" size=4096, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, name="swap" size=, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="data" EOF这种写法可维护性最好,改分区大小时只动对应的size,不用重算后面所有分区的起始位置。
4.3 用 --no-reread 和 --force 处理特殊场景
sfdisk写完分区表后,默认会通知内核重新读取分区表(reread)。但有些情况下内核读不了——比如磁盘正在被使用、有分区已挂载、或者设备是只读的。这时候sfdisk会报错退出,但分区表其实已经写进去了。
遇到这种情况,加--no-reread跳过内核重读:
sudo sfdisk --no-reread /dev/sdb < recipe.txt写完之后手动触发重读,或者重启生效。手动触发可以用partprobe /dev/sdb或者blockdev --rereadpt /dev/sdb,但如果分区正在使用,这两个命令也可能失败,那就只能等重启。
--force参数用于强制操作,比如磁盘上已经有分区表、sfdisk检测到可能有数据时会拒绝写入,加--force可以绕过这个检查。这个参数要慎用,它关掉的是安全防护,不是错误处理——分区表写坏了它不会帮你兜底。
注意:
--force和--no-reread经常一起用,但含义完全不同。前者是"我知道有风险,照写",后者是"写完别通知内核"。两个都加上意味着既不检查也不重读,适合在离线环境(比如装机阶段、救援模式)里操作,在线环境慎用。
5. 踩过的坑与排查链路
5.1 分区没对齐导致的性能问题
早些年我在一批虚拟机上批量装机,用的是一份从老机器上导出的分区配方,start全是裸扇区号。装完之后系统能跑,但磁盘 IO 性能比预期低了一截,iostat里await明显偏高。排查了半天,最后发现问题出在分区对齐上:那份老配方是按 63 扇区起始的(老式 MBR 惯例),而新环境是 4K 物理扇区的存储,63 × 512 = 32256 字节,不是 4096 的整数倍,每次读写都跨物理扇区。
排查链路是这样的:先用sfdisk -l看Start列,发现起始扇区是 63 而不是 2048;再用cat /sys/block/vda/queue/physical_block_size确认物理扇区是 4096;两个数字一除,32256 / 4096 = 7.875,不是整数,对齐问题坐实。修复方法很简单,把配方里的start改成带单位的写法(start=1MiB),让sfdisk自己算对齐,重新分区后性能恢复正常。
这个坑的教训是:永远不要裸写扇区号,用带单位的 start/size。sfdisk的对齐逻辑比人脑可靠,你告诉它"从 1MiB 开始",它会自动算出正确的扇区号,不管底层是 512 还是 4096 字节物理扇区。
5.2 分区表写了但内核不认
另一个高频问题是分区表写成功了,但lsblk看不到新分区。原因通常是内核没有重新读取分区表。sfdisk默认会尝试重读,但如果目标磁盘上有分区正在挂载,重读会失败,sfdisk报一个警告但分区表已经写入。
排查步骤:先看sfdisk -l /dev/sdX能不能看到新分区(能看到说明分区表写成功了),再看lsblk是否同步(不同步说明内核没重读)。解决方法是partprobe /dev/sdX手动触发,如果还不行,检查是否有分区挂载在目标盘上,卸载后再partprobe。实在不行就重启,重启时内核会重新扫描所有磁盘的分区表。
还有一种情况是设备本身不支持分区表重读,比如某些虚拟化平台的 virtio 磁盘在热插拔场景下。这种只能通过重新扫描 SCSI 总线或者重启解决,没有更好的办法。
5.3 GPT 备份表不一致的修复
GPT 分区表在磁盘开头存一份主表,末尾存一份备份表。如果只改了主表没更新备份表(比如用 dd 直接写扇区、或者某些工具异常中断),sfdisk会检测到不一致并拒绝操作,报错信息里会提到 "GPT PMBR size mismatch" 或 "backup GPT table is not on the end of the device"。
修复方法是让sfdisk用主表重建备份表:
sudo sfdisk --force /dev/sdb < /dev/null这条命令会清空分区表——等等,这不是修复,这是清空。正确的修复姿势是用gdisk的修复功能,或者用sfdisk重新写入一份完整的分区定义(主表和备份表会一起写)。如果只是想同步备份表而不动分区内容,可以用sgdisk -e /dev/sdb把备份表移到磁盘末尾,或者sgdisk -v检查并修复。
我遇到过一次备份表损坏的情况,是在一块被反复分区格式化的测试盘上。当时sfdisk -l能正常显示分区,但sfdisk写入时报备份表错误。最后用sgdisk -e把备份表重定位到末尾,问题解决。这个坑提醒我:GPT 盘的备份表不是摆设,异常断电、强制拔盘都可能导致备份表损坏,定期用sfdisk --verify检查一下没坏处。
5.4 误操作写错盘的止损思路
分区操作最怕的就是写错盘。我见过有人把/dev/sda当成数据盘,结果把系统盘的分区表覆盖了。这种事故一旦发生,第一反应应该是停止一切写操作,不要再往那块盘上写任何东西。
止损的第一步是确认损失范围:用sfdisk -l看当前分区表状态,如果分区表被覆盖但数据区没动,理论上数据还在,只是"目录"丢了。这时候可以用testdisk之类的工具扫描磁盘,尝试重建分区表。如果分区表覆盖后还做了格式化或写入了新数据,恢复难度就大很多。
预防永远比补救重要。我的习惯是:操作前用lsblk -o NAME,SIZE,MODEL,SERIAL把目标盘的序列号记下来,写命令时用/dev/disk/by-id/下的稳定路径而不是/dev/sdX(后者在重启或热插拔后可能变)。多花十秒钟确认,能省掉几小时的恢复工作。
6. 几个让效率翻倍的使用习惯
6.1 用 --verify 做写后自检
sfdisk --verify会检查分区表的一致性,包括主表和备份表是否匹配、分区是否重叠、是否有越界分区等。写完分区表后跑一遍,能提前发现很多隐患:
sudo sfdisk --verify /dev/sdb这个命令不修改任何东西,纯检查,跑起来很快,建议养成写完就 verify 的习惯。尤其是脚本化批量操作时,在每台机器上 verify 一遍,比事后出问题再排查省事得多。
6.2 结合 lsblk 做交叉验证
sfdisk -l看的是磁盘上的分区表,lsblk看的是内核识别到的块设备树。两者交叉验证能发现很多问题:分区表里有但lsblk里没有,说明内核没重读;lsblk里有但分区表里没有,说明有残留的设备节点。我通常的操作顺序是:sfdisk -l确认分区表 →lsblk确认内核识别 →partprobe同步 → 再lsblk确认。
lsblk -f还能显示每个分区的文件系统类型和 UUID,格式化之后用它确认挂载配置里的 UUID 对不对,比翻/etc/fstab直观。
6.3 把配方纳入版本管理
分区配方本质上是基础设施配置的一部分,应该跟其他配置文件一样纳入版本管理。我的做法是在配置仓库里建一个partitions/目录,每类机器一份配方文件,文件名带上用途和磁盘规格,比如web-server-500g.txt、db-node-2t.txt。装机脚本根据机器角色选择对应配方,sfdisk直接读取。
这样做的好处是分区布局可追溯、可复现、可评审。改分区方案时走正常的代码评审流程,比在机器上手动敲命令靠谱得多。配方文件里加上注释说明每个分区的用途,半年后回来看也能快速理解当初的设计意图。
6.4 处理大容量磁盘的注意事项
超过 2TB 的磁盘必须用 GPT 分区表,MBR 撑不住。sfdisk默认对空盘用 dos(MBR),所以大容量盘一定要在配方里显式写label: gpt。另外,大容量盘的分区表操作耗时更长,sfdisk写入后内核重读也可能更慢,脚本里要留足超时时间。
还有一个细节:某些老版本的sfdisk对超大 GPT 盘的支持有 bug,写入的分区表可能不完整。如果处理 4TB 以上的盘,建议先确认sfdisk --version,util-linux 2.30 以上版本对 GPT 的支持比较完善。版本太老的话,升级 util-linux 或者改用sgdisk更稳妥。
分区这件事,工具只是手段,真正重要的是对分区表结构的理解和对风险的敬畏。sfdisk的脚本化能力让它成为自动化场景下的首选,但它的"无交互"特性也意味着没有二次确认的机会——命令敲下去就执行了。所以每次操作前多跑一遍lsblk,多看一眼设备名,把配方纳入版本管理,这些习惯比任何技巧都值钱。我在实际使用中最大的体会是:把分区布局当成代码来管理,而不是当成一次性的手工操作,很多坑自然就绕过去了。