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 查询三板斧

  1. 查包是否安装
dpkg -l | grep package_name

输出第一列是状态码:ii 表示正常安装,rc 表示已卸载但残留配置,un 表示从未安装。

  1. 查包安装了哪些文件
dpkg -L package_name

这个命令在排查「某个文件到底属于哪个包」时特别有用。我经常用它来确认库文件是否被正确安装到了目标路径。

  1. 查某个文件属于哪个包
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 的核心操作、内部结构、查询命令串在了一起。建议你保存下来,以后排查问题时对照着看。

dpkg 与 rpm 知识体系 dpkg (Debian 系) 内部结构 ar 归档 control.tar.gz data.tar.gz 安装 / 卸载 dpkg -i dpkg -r / -P apt-get install -f 查询命令 dpkg -l 列出已安装 dpkg -L 列出包文件 dpkg -S 文件归属查询 数据库位置 /var/lib/dpkg/ rpm (Red Hat 系) 内部结构 CPIO 归档 头部元数据 GPG 签名 安装 / 卸载 rpm -ivh rpm -e rpm -e --nodeps 查询命令 rpm -qa 列出所有 rpm -ql 列出包文件 rpm -qf 文件归属查询 数据库位置 /var/lib/rpm/ 核心区别:.deb 元数据分离可手动修改,.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 直接搞定。