4. dpkg 与 rpm:底层包格式解析、手动安装与卸载、包信息查询
聊到 Linux 包管理,大家第一反应可能是 apt 或 yum。但说实话,这些高级工具只是「前台服务员」,真正干活的底层是 dpkg 和 rpm。我早年做嵌入式交叉编译时,经常遇到目标板上没有网络、没有源仓库,只能靠手搓 .deb 或 .rpm 文件来部署。那时候我才真正意识到——不懂底层格式,你连包坏了都修不了。
这一节,我们就来扒开这两大包格式的底裤。
4.1 包格式的「基因」差异
Debian 系用 .deb,Red Hat 系用 .rpm。它们本质上都是归档文件,但内部结构完全不同。
| 对比维度 | dpkg (.deb) | rpm (.rpm) |
|---|---|---|
| 归档格式 | ar 归档 + tar.gz 数据 | CPIO 归档 + 头部元数据 |
| 元数据位置 | 独立的 control.tar.gz | 嵌入在 RPM 头部 |
| 脚本支持 | preinst / postinst / prerm / postrm | %pre / %post / %preun / %postun |
| 依赖表达 | 精确版本号 + 替代关系 | 版本区间 + 虚拟包 |
| 数据库位置 | /var/lib/dpkg/ | /var/lib/rpm/ |
你看,虽然都是包,但「基因」完全不同。我曾在项目中遇到过把 .rpm 强行改后缀成 .deb 然后直接 dpkg -i 的同事……嗯,结果当然是报错。别笑,真有人这么干。
4.2 手动安装与卸载:裸操作才是真功夫
高级工具帮你自动解依赖,但底层工具只做一件事——按指令操作。你让它装,它就装;依赖缺了?它直接报错退出。
4.2.1 dpkg 的安装与卸载
安装一个 .deb 文件:
sudo dpkg -i package.deb
如果缺依赖,它会告诉你缺什么,但不会帮你装。这时候你需要手动补:
sudo apt-get install -f # 修复依赖
卸载包:
sudo dpkg -r package_name # 保留配置文件
sudo dpkg -P package_name # 彻底清除(含配置文件)
dpkg -r 后面跟的是包名,不是文件名。我见过有人写 dpkg -r package.deb,结果系统提示「找不到该包」——因为 -r 操作的是已安装的包名,不是磁盘上的文件。
4.2.2 rpm 的安装与卸载
安装 .rpm 文件:
sudo rpm -ivh package.rpm
参数说明:-i 安装,-v 显示详情,-h 打印进度条。我个人习惯在脚本里只用 -iv,因为进度条在日志里看着乱。
卸载:
sudo rpm -e package_name
这里有个坑:rpm -e 如果遇到依赖它的其他包,会直接拒绝卸载。你可以加 --nodeps 强制卸载,但后果自负——我曾经在服务器上强制卸载了 glibc 的某个子包,结果 ls 命令都跑不起来了。嗯,那台机器最后只能重装。
4.3 包信息查询:诊断问题的利器
系统出问题了,第一件事就是查包信息。底层工具提供了非常细粒度的查询能力。
4.3.1 dpkg 查询三板斧
- 查包是否安装
dpkg -l | grep package_name
输出第一列是状态码:ii 表示正常安装,rc 表示已卸载但残留配置,un 表示从未安装。
- 查包安装了哪些文件
dpkg -L package_name
这个命令在排查「某个文件到底属于哪个包」时特别有用。我经常用它来确认库文件是否被正确安装到了目标路径。
- 查某个文件属于哪个包
dpkg -S /path/to/file
比如你发现 /usr/bin/openssl 被覆盖了,用这个命令就能找到罪魁祸首是哪个包。
4.3.2 rpm 查询四件套
| 命令 | 作用 | 示例 |
|---|---|---|
rpm -qa |
列出所有已安装包 | rpm -qa | grep kernel |
rpm -qi |
查看包详细信息 | rpm -qi bash |
rpm -ql |
列出包安装的文件列表 | rpm -ql nginx |
rpm -qf |
查询文件属于哪个包 | rpm -qf /etc/passwd |
rpm -qi 输出的信息里有一项叫 Signature,可以验证包的 GPG 签名是否有效。在嵌入式安全加固场景下,我建议每次安装前都检查签名,防止被投毒。
4.4 包格式内部结构拆解
光会用还不够,我们来看看包里面到底长什么样。
4.4.1 .deb 包的三层结构
一个 .deb 文件其实是一个 ar 归档,里面包含三个文件:
ar t package.deb
debian-binary
control.tar.gz
data.tar.gz
- debian-binary:版本号,就一行
2.0。 - control.tar.gz:元数据 + 安装脚本。里面包含
control(包名、版本、依赖)、preinst、postinst等。 - data.tar.gz:实际要安装的文件。解压后直接覆盖到根目录。
你可以手动解包看看:
mkdir deb_extract && cd deb_extract
ar x ../package.deb
tar xf control.tar.gz
tar xf data.tar.gz
我当年调试一个嵌入式设备的 Wi-Fi 驱动时,就是靠手动修改 postinst 脚本,把固件文件拷贝到正确位置,然后重新打包安装的。这种操作在高级工具里根本做不到。
4.4.2 .rpm 包的头部 + 负载结构
.rpm 的结构更紧凑,没有独立的控制文件。它的元数据全部编码在二进制头部里:
rpm2cpio package.rpm | cpio -idmv
这个命令会把 .rpm 里的文件直接解压到当前目录。你可以看到 rpm 没有单独的 control 目录——所有依赖、脚本、签名信息都嵌在头部。
核心区别总结:
.deb是「元数据 + 数据」分离,便于手动修改。.rpm是「头部 + 负载」一体,校验更严格。
4.5 知识体系总览
下面这张图把 dpkg 和 rpm 的核心操作、内部结构、查询命令串在了一起。建议你保存下来,以后排查问题时对照着看。
4.6 避坑指南与实战建议
最后分享几个我踩过的坑,希望能帮你省点时间:
- 不要混用 dpkg 和 apt 操作同一个包:比如你先
dpkg -i装了一个旧版本,然后apt upgrade想升级——apt 会认为这个包是「手动安装」的,不会自动升级。我建议要么全用 apt,要么全用 dpkg,别混着来。 - rpm 卸载前先查依赖:用
rpm -q --whatrequires看看有没有其他包依赖它。我曾经在嵌入式开发板上卸载了一个看似无用的库,结果系统服务全挂了。 - 手动解包修改后,记得重新计算校验和:
.deb包没有强制校验,但.rpm头部有签名。如果你修改了.rpm的内容,安装时会报签名错误,需要加--nosignature跳过。
我的个人习惯:在嵌入式开发中,我会先用 dpkg -L 或 rpm -ql 确认包的文件列表,然后手动对比目标板上的文件是否完整。如果发现缺失,直接解包拷贝,比重新编译快得多。
好了,dpkg 和 rpm 的核心操作就这些。记住一句话:高级工具帮你省事,底层工具帮你救命。下次遇到包管理问题,别急着搜 apt 命令,先想想能不能用 dpkg 或 rpm 直接搞定。