不为人知的 pkg(8) 功能
作者:𝚟𝚎𝚛𝚖𝚊𝚍𝚎𝚗
2019/01
我多次被要求撰写一篇关于 pkg(8) 的文章——这是 FreeBSD 现代软件包管理器,有时也称为 PKGng。
在本篇文章中,我将尝试介绍一些不太为人所知的 pkg(8) 功能。
大约在 8 年前——当时 pkg(8) 还未问世——我写过一篇帖子:HOWTO: Keeping FreeBSD’s Base System and Packages Up-to-Date。后来该文章甚至刊载于 BSD Magazine 2012/01 期(Issue 30)。

回到 2011 年,软件包更新比现在稍微复杂一些。那时,你不得不使用 FreeBSD 的 STABLE 分支,因为 RELEASE 分支的软件包几乎不更新——类似于当前 OpenBSD 的情况。FreeBSD STABLE 分支上的软件包每两周构建一次,当时已经足够使用。
当然,你也可以使用 portmaster 从 FreeBSD Ports 编译所有软件包,但这会浪费大量时间。当时 pkg_add/pkg_delete/pkg_info 是 FreeBSD 上的主要软件包工具,而 bsdadminscripts 包中的 pkg_upgrade 脚本在升级过程中非常有用。它会从 STABLE 分支的 FTP 服务器获取最新的软件包并更新已安装的软件包。要检查软件包的安全问题,则需要另一款第三方工具 portaudit。
现在,我们有了功能完善的 pkg(8),能使用 pkg upgrade 来更新安装的软件包。得益于 pkg audit,已不再需要第三方工具 portaudit 了。我们甚至还有 pkg autoremove 来自动移除不再需要的依赖。
我会尽量不重复已经非常优秀的 FreeBSD Handbook 中 4.4. 使用 pkg 管理二进制软件包 章节中已有的信息。
较旧的 FreeBSD 版本
在 FreeBSD 10 之前,如果要使用新的 pkg(8) 工具,非旧的 pkg_* 工具,则需要在 /etc/make.conf 文件中加入 WITH_PKGNG=yes。
目前受支持的 FreeBSD RELEASE 版本只有最近发布的 12.0 以及更加稳定和完善的 11.2,因此无需在 /etc/make.conf 文件中加入某些内容来使用 pkg(8) 框架。
数据库
pkg(8) 数据库(实际上是 SQLite 数据库)保存在目录 /var/db/pkg。
下面是 pkg(8) 引导过程之后目录 /var/db/pkg。
最重要的文件是 /var/db/pkg/local.sqlite,因为它是已安装包及其文件的数据库。在输入 pkg shell 后,你实际上可以通过 SQLite 解释器连接到这个 SQLite 数据库。
若由于某种原因你发现 pkg(8) 工具无法工作或已经损坏,你可以使用 sqlite3 包中的 sqlite3 命令连接到它。不要使用软件包 sqlite,因为它是 SQLite 的 2.x 版本,而这与 pkg(8) 使用的 3.x 版本不向前兼容。
锁定/解锁
使用 pkg(8),现在可以用 pkg lock 命令锁定指定的包。这意味着 pkg upgrade、pkg delete(甚至 pkg autoremove)操作都不会影响它们。你可以使用选项 -l 列出已锁定的包,如下所示。
如你所见,无法 pkg delete 已锁定的包 exfat-utils。你必须先用 pkg unlock 命令将其解锁。你可以交互式地进行,也可以使用选项 -y 非交互式完成,如下所示。
为什么要锁定某些软件包?
根据我的经验,以下是需要锁定特定软件包的潜在原因:
你将 Ports 和软件包结合使用。
某个 Port 并不存在对应的软件包。
官方软件包的默认编译选项与你自己的不同。
你确实想使用旧版本的软件包。
实际上,我使用 lock/unlock 机制,是因为上述所有情况对我都成立。
我会同时使用 Ports 和软件包(在 FreeBSD 世界中这通常不被推荐),因为我使用的一些软件由于授权问题无法提供为软件包。例如所有和 Microsoft exFAT 文件系统相关的东西(exfat-utils/fusefs-exfat)以及 MP3(lame)。更令我惊讶的是,OpenBSD 多年来一直提供 lame 软件包,而 FreeBSD 团队仍然害怕专利问题。
我还需要构建自定义版本的 ffmpeg 软件包——只是为了加入 lame 支持,但仍然是自定义的。最后一个我保持锁定的是 Conky。它在 1.9 版本时很好用,但开发者在 1.10 版本(现在甚至已有 1.11)把它彻底搞坏了。比如,你无法在桌面上右键点击得到 Openbox 菜单——换句话说,Conky 不再把鼠标事件传递给负责桌面的 Window Manager。
于是我使用了 Ports 的另一个工具 portdowngrade,把 1.9 版本的文件提取到 Ports,然后编译出 1.9 的 conky 软件包并永久锁定。
你可能已经知道,我更喜欢使用 dzen2 来显示界面信息,但我仍然会在需要时用 conky 做“FreeBSD Dashboard”,然后按下 [Scroll Lock] 键临时启用它。
提供项
如果你同时也是 RHEL/Fedora(或一般 yum/rpm)的用户,你可能会在 FreeBSD 的 pkg(8) 包管理器中怀念“provides”这个功能。为什么它如此有用?因为有了“provides”数据库,你可以直接通过指定软件包中某个二进制或文件的确切名称来安装软件包。例如你可以输入 yum install /sbin/ifconfig 来安装 net-tools 包,因为“provides”数据库中含有该信息。
如果我告诉你使用 pkg(8) 也能实现类似的功能呢?
pkg-provides 插件能让你直接使用 pkg(8) 查询哪个软件包提供了某个特定的文件。
它甚至已经作为软件包 pkg-provides 提供。下面我将展示如何安装配置。首先安装软件包 pkg-provides。
然后配置文件 /usr/local/etc/pkg.conf。
现在你就有了新命令 pkg provides,如下所示。
你可以使用选项 -u 来更新 ‘provides’ 数据库。
pkg provides 插件示例用法
虽然例如无法通过输入 pkg install /compat/linux/usr/bin/pldd 命令来安装包 linux_base-c7,但可以检查哪个包包含该文件。
下次你执行命令 pkg upgrade 时,也会看到 provides 数据库的刷新。
pkg provides 数据库在目录 /var/db/pkg 中会占用相当量的空间。
如果你使用像 LZ4 这样的 ZFS 压缩,那么它占用的空间就不会太大,如下所示。
……但是如果你使用 UFS,那么这个将近 600 MB 的数据库可能会让你有点吃惊 :🙂:
Which
虽然 pkg provides 可以提供尚未安装的软件包中文件的相关信息,pkg which 命令则是经典 UNIX which 命令在 pkg(8) 中的对应。它显示某个文件属于哪个软件包(或者根本不属于任何软件包)。
双管齐下,乐趣加倍
有时同时使用两个 which 命令会更快地得到所需的答案。
periodic 定期任务
有时你可能会看到如下情况。
…但是你并没有启动任何其他 pkg(8) 实例,这到底是怎么回事呢?我们来看一下 ps(1) 的输出。
FreeBSD 的 periodic 脚本正在执行它们的工作。
要查看具体是哪些脚本,可以查看这里。
如果你认为这些活动中某些是不必要的,可以在 /etc/periodic.conf 文件中使用这些值将它们禁用。
例如,如果你想禁用 /usr/local/etc/periodic/daily/490.status-pkg-changes 的执行,你需要在 /etc/periodic.conf 文件中添加 daily_status_pkgng_changes_enable=no。
接下来再检查一次 ps(1) 输出。
periodic 任务已经完成。你现在可以像平常一样安装你的包了。
统计信息
虽然 pkg stats 命令可以提供已安装包的一些统计信息,但它对于查找哪个包占用空间最多并不是很有用。
还有 pkg size 命令,它只会显示包占用的空间,但不会显示包名……没多大用处。
此外,pkg size 的手册页并不存在。
你可以使用 pkg info -as 命令,但它不仅不会对输出进行任何排序,还会以 KiB/MiB/GiB 等不同单位显示空间使用情况,这并不方便……幸运的是,sort 命令的选项 -h 能帮上忙。
使用以下别名可以按空间使用量对包进行排序。我将输出限制为最大 20 个包,但你可以根据需要修改。
缩写
pkg(8) 工具同样支持缩写参数。例如,你不必输入完整的 pkg autoremove,只需输入 pkg autor 即可执行该命令。
下面是简短名称示例。
元数据

许多 pkg(8) 的问题都是由旧的元数据数据库引起的。如果你遇到任何 pkg(8) 问题,首先应如下面所示强制刷新其数据库。
为了记录——在这个过程中,“provides” 数据库也会被刷新。
修复损坏的依赖
曾经有段时间,缺失的软件包 www/libxul19 依赖问题折磨了我一阵子。
我甚至绝望地准备用 portmaster 重新编译所有东西。
我从命令 portmaster --check-depends 开始,但在系统询问是否修复时选择了“n”,因为这会不必要地降级大量软件包。
让我们看看 pkg(8) 显示我们已经安装了哪些软件包。
问题在于我们安装了 www/libxul 而不是 www/libxul19,这就是为什么 portmaster(不仅仅是它)会报错的原因。
在 pkg(8) 被引入之前,只需使用 grep -r 搜索整个 /var/db/pkg 目录及其“文件数据库”就很容易,但现在情况复杂得多,因为软件包数据库保存在 SQLite 数据库中。使用 pkg shell 命令,你可以连接到该数据库。让我们看看能找到什么。
所以现在我们知道,“deps” 表可能就是我们要找的 ;)。
由于 pkg shell 在浏览 SQLite 时功能相当有限,我将直接使用命令 sqlite3。所谓有限是指,你不能直接输入 pkg shell "select * from deps;" 这样的查询,而是需要先启动 pkg shell,然后才能输入查询语句。
第二列是 name,所以我们可以尝试使用它。
现在我们已经找到了“有问题”的依赖条目,可以稍微修改它,使其与实际已安装的包状态一致。
当然,你也可以使用“官方”方法,通过 pkg shell 命令来操作。
现在 portmaster 感到满意,不会再命令依赖缺失了。
太棒了!问题解决了 :😉:
……但 pkg(8) 已经自带一个工具可以做到这一点 :🙂:
它叫 pkg set,在 man pkg-set 中最有用的两个选项是。
在我们的例子中,我们会使用 pkg set -o www/libxul19:www/libxul 命令。
不确定它是否能以同样方式解决问题,因为我在数据库中也更新了版本。
更新(UPDATING)
如果在执行 pkg upgrade 命令时遇到任何问题,那么你也应该查看最新版本的 /usr/ports/UPDATING 文件——例如可以在使用 portsnap fetch update 命令更新 Ports 后获取。
该文件描述了 Ports 中的重要变更(以及由于包是由 Ports 构建而来的,因此也涵盖了包的变更)。
pkg(8) 框架也提供了相应工具,即 pkg updating 命令。具体细节可查看 man pkg-updating 页面。最常见的用法是使用参数 -d 并指定日期,如下所示。
你也可以在 https://www.freshports.org/UPDATING 在线查看 UPDATING 文件。
使用 ZFS 启动环境进行稳健升级
为了绝对确保无论 pkg upgrade 命令出现何种问题,你的系统都能正常工作,可以使用 ZFS 启动环境。我不久前曾在 波兰 PBUG 和 荷兰 NLUUG 介绍过其功能。仍可在链接 https://is.gd/BECTL 下载最新的 PDF 演示文稿。
使用 beadm 命令的操作流程如下。
现在,即使出现任何问题,你依然拥有名为 safepoint 的完整可用系统启动环境。
只需重启并选择该环境(在 FreeBSD loader 中选择),你就能恢复到正常工作的系统,就像使用时间机器回到过去一样。
查询
你也可以使用命令 pkg query 来查找所需的信息。
例如,要“模拟” pkg info -r pkg-name 参数(显示依赖 pkg-name 的软件包列表),可以使用如下方式的 pkg query 命令。
如果你想知道每个包的初次安装时间,可以使用下面这个方法。
你也可以显示那些不会被命令 pkg autoremove 移除的包,因为它们是你直接安装的。
罗塞塔石碑
FreeBSD Wiki 页面也提供了一些表格,但信息不完整。
因此我复制了表格并补充了缺失的数据。
下面是旧 pkg_* 工具与当前 pkg(8) 框架的对照表(更新版):
功能
旧 pkg_* 工具
新 pkg(8) 工具
列出已安装的包
pkg_info
pkg info
获取包的基本信息
pkg_info pkgname-pkgversion
pkg info pkgname pkg info category/name pkg info pkgname-pkgversion
获取包的详细信息
N/A
pkg info -f pkgname pkg info -f category/name pkg info -f pkgname-pkgversion
列出已安装包中的所有文件
pkg_info -L pkgname-pkgversion
pkg info -l pkgname pkg info -l category/name pkg info -l pkgname-pkgversion
查找哪个包提供某个文件
pkg_info -W /path/to/my/file
pkg which /path/to/my/file
安装本地包
pkg_add ./localpkg.tbz
pkg add ./localpkg.txz
安装远程包
pkg_add -r mypackage
pkg install mypackage pkg install category/name pkg install pkgname-pkgversion
搜索远程包
ls /usr/ports/*
grep mypackage
搜索远程包的详细信息
make search name=mypackage make search key=mypackage
pkg search -f mypackage pkg search -f category/name pkg search -f pkgname-pkgversion
已安装包的反向依赖
pkg_info -R pkgname-pkgversion
pkg info -r mypackage pkg info -r category/name pkg info -r pkgname-pkgversion
已安装包的依赖
pkg_info -r pkgname-pkgversion
pkg info -d mypackage pkg info -d category/name pkg info -d pkgname-pkgversion
删除作为依赖安装的未使用包
N/A
pkg autoremove
二进制升级已安装包
pkg_upgrade (FreeBSD Ports)
pkg upgrade
创建远程仓库
N/A
pkg repo /directory/with/packages
操作 jail 中的包
N/A
pkg -j
操作 chroot 中的包
pkg_add -C
pkg -c
使用正则表达式获取已安装包信息
pkg_info -x
pkg info -x
使用扩展正则表达式获取已安装包信息
pkg_info -X
pkg info -X
使用通配符获取已安装包信息
pkg_info
pkg info -g
检查已知漏洞
portaudit (FreeBSD Ports)
pkg audit
过期包
pkg_version -l <
pkg version -l <
过期包(安装信息详细)
pkg_version -Il <
pkg version -Il <
与远程仓库对比的过期包
N/A
pkg upgrade -n
已安装包统计信息
N/A
pkg stat
检查缺失依赖(并修复)
N/A
pkg check -d
包来源
pkg_info -o
pkg info -o
如果你知道其他有用的 pkg(8) 命令,也可以告诉我 🙂
最后更新于
这有帮助吗?