Linux 软件管理

内容纲要

查看【Linux】专题可浏览更多内容

你应该已经很熟悉从应用商店或互联网上的软件官网获取并安装软件的安装方式。

但在 Linux 上,你也许已经有所耳闻通过「包管理」和通过软件源代码「编译」安装,那么具体是如何操作的?

软件包管理器

打包系统(packaging system)的出现简化并推进了软件管理工作。软件包中含有运行软件所需要的全部文件,包括预编译好的二进制文件、依赖信息、可由管理员定制的配置文件模板等。其中最重要的可能要算打包系统力图使安装过程尽可能的原子化(atomic),如果安装发生了错误,可以撤销(backed out)或是重新安装软件包,当有新版本时只用更新软件包即可,非常简便。

打包系统通常由两种工具组成一种双层(dual-layer)的全能配置管理工具:

  • 处理包(package)文件的安装和删除等任务的低层(Low-Level)工具。
  • 执行元数据搜索和解决依赖性问题的高层(High-Level)工具。

下层的工具负责安装、卸载、查询软件包,上层对应系统负责从 Internet 上查找并下载软件包、分析软件包之间的依赖关系、更新系统中所有的软件包。

低层(Low-Level)工具

基本上每个发行版默认都有一套包管理器,你可能已经见过 .deb 或是 .rpm 这样格式的软件包了,它们就像是 macOS 上的 .dmg 或是 Windows 上的 .exe,在各个软件的官方网站提供下载。

dpkg

.deb 由「dpkg(Debian Package Management System)」 进行管理,常见于 Debian 及其谱系如 Ubuntu、Linux Mint 这样的 Linux 发行版上使用。

以 VirtualBox 虚拟机为例,演示如何使用 dpkg 管理 .deb 软件包:

首先下载 VirtualBox 的 .deb 安装包,你可以在 VirtualBox 的官网找到它:https://www.virtualbox.org/wiki/Downloads

你可以使用浏览器或 wget 下载安装包:

wget https://download.virtualbox.org/virtualbox/6.1.4/virtualbox-6.1_6.1.4-136177~Ubuntu~bionic_amd64.deb

virtualbox-6.1_6.1.4-136177~Ubuntu~bionic_amd64.deb 文件名拆解来看:

  • virtualbox 软件名
  • 6.1_6.1.4-136177 软件版本
  • Ubuntu~bionic Linux 发行版
  • amd64 系统架构
  • deb 软件包管理格式

软件包名也是有所需要注意的地方,如上需要最注意后两位也就是 amd64debamd64 适用于 x86 架构的 64 位系统,也就是不能安装在其他架构比如 ARM 上,而 deb 如 Debian 和 Redhat 是分别使用 deb 和 rpm 的软件包格式,默认是不包含另一套包管理工具的,也就是说不能把 deb 软件包拿到默认情况下使用 rpm 包管理的发行版上使用,反之亦然。

在确定了软件包获取正确后开始安装软件:

# 使用 dpkg 命令,-i 选项表示 install 安装,-i 等同 --install
sudo dpkg -i virtualbox-6.1_6.1.4-136177~Ubuntu~bionic_amd64.deb

⚠️ 注意:但有时候你可能会遇上依赖问题,如下类似的报错:

dpkg: 依赖关系问题使得 virtualbox-6.1 的配置工作不能继续:
 virtualbox-6.1 依赖于 libcurl4 (>= 7.16.2);然而:
  未安装软件包 libcurl4。
 virtualbox-6.1 依赖于 libqt5core5a (>= 5.9.0~beta);然而:
  未安装软件包 libqt5core5a。
 virtualbox-6.1 依赖于 libqt5gui5 (>= 5.4.0);然而:
  未安装软件包 libqt5gui5。
 virtualbox-6.1 依赖于 libqt5opengl5 (>= 5.0.2);然而:
  未安装软件包 libqt5opengl5。
 virtualbox-6.1 依赖于 libqt5printsupport5 (>= 5.0.2);然而:
  未安装软件包 libqt5printsupport5。
 virtualbox-6.1 依赖于 libqt5widgets5 (>= 5.7.0);然而:
  未安装软件包 libqt5widgets5。
 virtualbox-6.1 依赖于 libqt5x11extras5 (>= 5.6.0);然而:
  未安装软件包 libqt5x11extras5。
 virtualbox-6.1 依赖于 libsdl1.2debian (>= 1.2.11);然而:
  未安装软件包 libsdl1.2debian。
dpkg: 处理软件包 virtualbox-6.1 (--install)时出错:
 依赖关系问题 - 仍未被配置
正在处理用于 systemd (237-3ubuntu10.39) 的触发器 ...
正在处理用于 ureadahead (0.100.0-21) 的触发器 ...
正在处理用于 gnome-menus (3.13.3-11ubuntu1.1) 的触发器 ...
正在处理用于 desktop-file-utils (0.23-1ubuntu3.18.04.2) 的触发器 ...
正在处理用于 mime-support (3.60ubuntu1) 的触发器 ...
正在处理用于 hicolor-icon-theme (0.17-2) 的触发器 ...
正在处理用于 shared-mime-info (1.9-2) 的触发器 ...
在处理时有错误发生:
 virtualbox-6.1

这个时候需要先解决依赖问题(安装日志上所提的未安装软件包),然后再次安装,对于补齐依赖还可以使用 APT 自动安装所需要的软件包:

# 补齐依赖
sudo apt install -f

可以通过 -l 参数查询是否安装了某个软件

# -l 表示 list
sudo dpkg -l virtualbox
# 或是
sudo dpkg -l | grep virtualbox

可以通过 -r 参数删除指定软件

# -r 表示 remove
sudo dpkg -r virtualbox-6.1
# -P 表示 pure 删除软件及其配置文件
sudo dpkg -P virtualbox-6.1

⚠️ 注意:在输入到 dpkg -r virtualbox 时可能不知道具体版本号或更具体的包名,这时可以使用 Tab 键列出可选项来补全结果。

在遇到需要处理安装或卸载多个软件包时,使用空格将每个软件包名隔开即可使用一条命令处理多个软件包。

小结

# 使用 -i 安装指定软件
sudo dpkg -i <软件包名>

# 使用 -l 查询安装的某个软件包
sudo dpkg -l <软件包名>
# 或是
sudo dpkg -l | grep <软件包名>

# 使用 -r 卸载指定软件
sudo dpkg -r <软件包名>

# 使用 -P 卸载软件及删除其配置文件
sudo dpkg -P <软件包名>

RPM

.rpm 由「RPM(RPM Package Manager ,最早称为 Red Hat Package Manager)」进行管理,常见于 Red Hat、OpenSUSE、Fedora 等 Linux 发行版使用。

RPM 的使用方法也类似于 dpkg:

使用 -i (install)安装指定软件

sudo rpm -i <软件包名>

使用 -q (query)查询指定软件

sudo rpm -q <软件包名>

# 列出系统中以安装的所有软件包
sudo rpm -qa <软件包名>

使用 -e (erase)卸载指定软件

sudo rpm -e <软件包名>

使用 -U (upgrade)更新指定软件,比如找到软件包的新版本,可以使用 rpm -U 来更新替换旧版本:

sudo rpm -U <软件包名>

但可能并不能一帆风顺的成功更新,因为新版本可能需要新的依赖,此时尽管你可以使用强制参数选项 --force,但这并不是推荐的做法,可以先查询所依赖的那些软件包的更新版本:

sudo rpm -q --whatrequires <软件包名>

高层(High-Level)工具

低层工具如 dpkg 用于直接管理 .deb 这样的软件包,而高层工具可以从软件仓库查询软件包信息,查找并获取软件包,在获得软件包后交由低层工具进行安装、升级软件包,以及解决软件包的依赖。

❓ 什么是依赖
假设说小明开发了一款视频播放器,但光有播放器还不够还需要解码器进行视频解码,而恰好业界有一个开源的解码器方案可以直接拿来使用,那么在安装小明的播放器时(根据包的设置)就需要安装所依赖的解码器方能正常使用。

❓ 什么是软件仓库
一般来说各个发行版都有自己维护的一套软件仓库,里面有浏览器、办公套件、播放器等等,用户需要什么就可以使用包管理器通过图形界面或命令行工具直接搜索并获取安装,这样的方案更直接快捷,避免了用户不知道哪个是软件官网所带来的麻烦和安全隐患。

APT

APT(Advanced Package Tool)是 Debian 及其衍生产品的主要命令行软件包管理器。它提供了用于搜索、管理和查询软件包信息的命令行工具,以及对 libapt-pkg 和 libapt-inst 库所提供的所有功能的低级访问,而这些功能是更高级别的软件包管理器可以依赖的。

一般来说发行版还提供了软件仓库的网页查询页面,如 Debian 的 Debian -- Packages,或是 Ubuntu 的 Ubuntu Packages Search
在网站上选择正在使用的发行版代号和想要查找软件包就能列出相关的结果。

如果使用 APT 管理来自标准仓库镜像中的安装,

软件仓库(源服务器)配置

在使用 APT 前不得不提到 APT 最重要的配置文件:/etc/apt/sources.list,以 Debian 10 官方镜像为例它的内容如下:

deb http://deb.debian.org/debian/ buster main
deb-src http://deb.debian.org/debian/ buster main

deb http://security.debian.org/debian-security buster/updates main
deb-src http://security.debian.org/debian-security buster/updates main

# buster-updates, previously known as 'volatile'
deb http://deb.debian.org/debian/ buster-updates main
deb-src http://deb.debian.org/debian/ buster-updates main

# This system was installed using small removable media
# (e.g. netinst, live or single CD). The matching "deb cdrom"
# entries were disabled at the end of the installation process.
# For information about how to configure apt package sources,
# see the sources.list(5) manual.

该配置文件告诉了 APT 应该从哪里获取软件包,具体构成如下:

  • type 字段:即软件包类型,debdeb-src 表示 .deb 类型的软件包、rpmrpm-src 表示 .rpm 类型的软件包;
  • uri 字段:指向可以从中获取到软件包的文件、HTTP 或 FTP 服务器的 URL;
  • distribution 字段:即发行版代号,如上述内容中的 buster 是 Debian 10 的代号,每个大版本都会有自己的代号;
  • components 字段:一个可能的组建清单(发行版本内的软件包分类)

举例说明:

deb http://deb.debian.org/debian/ buster main

<type> <uri> <distribution> <components>

distribution 字段和 components 字段帮助 APT 遍历软件仓库中文件系统的层次结构,这个结构的布局是有标准可循的:

  • distribution 字段是根发行版给每个发行起的 working title,如 Debian 10 的 buster,Ubuntu 20.04 的 focal;
  • components 字段的可用组件通常叫做 main、universe、multiverse、restricted;

在 Ubuntu 中除了基本的开源软件的 universe 组件还会有 multiverse 组件以使用一些非开源内容,如 VMware 等,也就是说如果想使用一些未受支持、存在受限许可的软件包可以加入 universe 和 multiverse 仓库。而 Debian 根据 《Debian自由软件指导方针》,所有包含在 Debian 正式发行版中的软件包,都是自由软件。

另外关于 uri 默认的配置也需要注意,默认的服务器配置可能并不适合你所在的区域,如压根就连接不上或速度极其缓慢,好在全球各地都有开源镜像站(甚至一些云服务商有自己本地的镜像站),这时候就近选择一台开源镜像服务器就可以加快速度提升使用体验。

例如 Debian 的 Debian worldwide mirror sites 和 Ubuntu 的 Official Archive Mirrors for Ubuntu 官方公布了位于全球各地的全球镜像站,可以放心的使用上面列出的镜像站或使用你自己信任的镜像站。

此处以使用「网易开源镜像站」为例通过命令快速修改源配置,特别是如果你还不熟悉 vim 等编辑器的使用,这是个比较友好的方法:

可以先使用 less /etc/apt/sources.list 查看配置中的域名是什么,如前所述我所安装的 Debian 10 中默认的地址为 deb.debian.orgsecurity.debian.org,此处我将将其修改为 mirrors.163.com

# 将 sources.list 中的 deb.debian.org 替换为 mirrors.163.com
sudo sed -i s/deb.debian.org/mirrors.163.com/g /etc/apt/sources.list

# 将 sources.list 中的 security.debian.org 替换为 mirrors.163.com
sudo sed -i s/security.debian.org/mirrors.163.com/g /etc/apt/sources.list

# 查看源文件以确认修改成功
less /etc/apt/sources.list

# 更新软件源
sudo apt update

⚠️ 这里需要提到的是,如 Debian 的 security.debian.org 和 Ubuntu 的 security.ubuntu.com 这样的地址是用于安全更新的,如 Linux Mint 等发行版有提供图形化的源服务器修改工具,在修改时上述用于安全服务的地址是不会被修改到的,包括一些 Linux 相关的专业书籍提到,一定要确保官方的安全服务器作为源加入,以便能够访问最新的安全补丁。

即便如此个人依然使用开源镜像站,因为默认地址在我这实在太慢了

APT 使用

在一些较老的文章中普遍使用 apt-get,而当下而言使用 apt 是更为现代的做法:

更新升级

# 更新软件源
sudo apt update

# 用于安装当前系统上安装的所有包的最新版本
sudo apt upgrade

# 使用 upgrade 命令升级时,如果为了满足新的依赖关系需要安装额外的软件包,则会保留软件包的旧版本。full-upgrade 命令则没有那么保守
sudo apt full-upgrade

 # 更新指定的软件包
sudo apt upgrade <软件包名>

# 可以使用 & 符号将多条命令一起执行
# 刷新软件源并更新需要更新的软件
# -y 参数用于自动确认提示,可用于 corn 定期更新
sudo apt update && sudo apt upgrade - y

软件查找以及软件信息查看

# 查找某个软件包
apt search <软件包名>

# 显示某个软件的信息
apt show <软件包名>

# 列出指定 (含已安装) 的软件包
apt list -a <软件包名>
# 可以使用通配符查找包含该名字的包,如和 NGINX 相关的包
apt list nginx*

软件安装及卸载

# 安装 .deb 包
# APT 会在幕后调用 dpkg,注意需要输入 .deb 包的路径
sudo apt /<文件路径>/<软件包名>.deb

# 安装某个软件包
sudo apt install firefox

# 卸载某个软件包
sudo apt remove firefox
# 卸载某个软件包及其配置文件
sudo apt purge firefox # 或 apt --purge remove firefox

# 卸载不需要的包
sudo apt autoremove

# 清除缓存包
# 默认会将安装和升级的软件包存储在 /var/cache/apt/archives/ 里
sudo apt clean

DNF

DNF 是 YUM 的下一个主要版本,它是一个基于 RPM 的 Linux 发行版的软件包管理器。

它大致保持了与 YUM 的 CLI 兼容性,并为扩展和插件定义了一个严格的 API。

# 检查可以更新的软件包
dnf check-update

# 更新可以升级的软件包
sudo dnf update # 或 dnf upgrade,更新系统中所有可以更新的软件包
sudo dnf update firefox # 更新指定的软件包

# 查找某个软件包
dnf search bash
# 显示某个软件包信息
dnf info bash
# 列出指定已安装的软件包
dnf list bash

# 安装某个软件包
sudo dnf install nginx
# 卸载某个软件包
sudo dnf remove nginx  # 或 dnf erase nginx

# 卸载不需要的包
sudo dnf autoremove

说到 Red Hat 系的发行版就不得不提到 EPEL(Extra Packages for Enterprise Linux),这是一个由 Fedora 特别兴趣小组创建、维护以及管理针对企业版 Linux 的一个高质量附加软件的包集。EPEL 是 Fedora 的一个精选包,但只有那些不在 RHEL 或其分层产品中的包,以避免冲突。所以你不会在 EPEL 中找到已经和 RHEL 一起发布的新版本的东西。

以 RHEL 来说,大多数人都知道 Fedora 是 RHEL 主要发行版的上游版本。出于多种原因,Red Hat 为 RHEL 提供的包集比在 Fedora 中的包集要小,因为 Fedora 中的很多软件在企业环境中是不需要的,或者不属于 RHEL 的范围。

如果作为 Fedora 用户并希望在 RHEL 工作站上安装 ImageMagick 或 Chromium 就需要 EPEL。也就是说,包括但不限于 Red Hat Enterprise Linux(RHEL)、 CentOS 和 Scientific Linux(SL)、 Oracle Linux(OL)的发行版可以添加 EPEL 仓库以使用更多的软件包。

但同时需要注意,这是一个开源社区主导的项目,它为用户提供了很多好处,但没有生产准备或技术支持的保证。所以 EPEL 项目建议 EPEL 的用户订阅 EPEL-announce 邮件列表,以了解即将发生的问题,并在更新产生影响之前有时间进行测试。

# 启用 EPEL 仓库
sudo dnf install epel-release

# 启用 PowerTools 存储库,因为 EPEL 包可能依赖于这些存储库中的包
sudo dnf config-manager --set-enabled powertools

源码构建

在 Linux 上除了使用软件包的形式,一种常见的的安装方式是使用软件的源代码编译安装。

编译就是将源代码翻译成计算机处理器原生语言编写的代码的过程。编译器会将高级语言转换成机器语言(有的编译器会先转成汇编语言,然后使用汇编器完成最后一步的机器语言转换)。

一般常见的 UNIX 和 Linux 的软件资源(源代码、构建文件、文档以及配置模板)的分发方式为采用压缩过的归档文件形式发布,通常是以 gzip 压缩的 tar 归档(.tar.gz.tgz)文件。

大体来说源码构建的步骤如下:

  1. 获取源代码包文件并解压到存放源码文件的目录 /usr/local/src/
  2. 进入解压出来的软件源码目录,使用命令 ./configure 配置及 make 编译来构建程序;
  3. 使用命令 make install 安装程序;

但在开始之前需要先安装一些工具,如用于编译源代码的编译器 gccg++、用于构建的 make 等等,这些实用程序一般来说并未预装在系统上,那么在在编译安装软件时就会看到这样的提示:

  1. configure: error: no acceptable C compiler found in $PATH
  2. make: command not found
# 使用 APT:
sudo apt install build-essential

# 使用 DNF:
sudo dnf group install "Development Tools"

然后就可以正式开始编译安装软件了,本文就以 iPerf3 为例(其他软件的流程也差不多的)

系统自行安装的源代码一般位于 /usr/src 目录,而面向多用户使用的源代码则通常位于 /usr/local/src 目录,先进入目录以存放源代码文件然后从 iPerf3 的官网使用 wget 获取源码包:https://iperf.fr/iperf-download.php

# 进入 /usr/local/src
cd /usr/local/src

# 下载源码包,因为当前是非特权用户所以使用 sudo
sudo wget https://iperf.fr/download/source/iperf-3.1.3-source.tar.gz

# 验证文件的完整性
sha256sum iperf-3.1.3-source.tar.gz
e34cf60cffc80aa1322d2c3a9b81e662c2576d2b03e53ddf1079615634e6f553  iperf-3.1.3-source.tar.gz

一般软件官网都会提供文件 sha256 或 md5 以验证文件的完整性,你可以使用命令 sha256summd5sum 来进行查验。

现在拿到了源代码包文件,一般来说源代码包文件需要遵循一定的源代码压缩标准,该标准其中一条规定是,在解压源代码的 .tar 文件时,会创建一个包含源代码树的目录,该目录的命名形式为 project-x.xx,其中包含了项目名称及版本号。

但你并不能保证所有人都遵循标准,如果所解压的项目并不遵循该标准就会将项目文件一股脑的全解压到 /usr/local/src 中,这样目录就会乱七八糟的,所以在解压前需要检查下:

tar tf iperf-3.1.3-source.tar.gz | head
iperf-3.1.3/
iperf-3.1.3/.gitignore
iperf-3.1.3/INSTALL
iperf-3.1.3/LICENSE
iperf-3.1.3/Makefile.am
iperf-3.1.3/Makefile.in
iperf-3.1.3/README.md
iperf-3.1.3/RELEASE_NOTES
iperf-3.1.3/aclocal.m4
iperf-3.1.3/bootstrap.sh

可以看到,文件都在 iperf-3.1.3 目录下,所以可以放心解压了:

sudo tar -xaf iperf-3.1.3-source.tar.gz

# 如果你一开始将源代码归档压缩文件放在其他地方,可以指定解包到 /usr/local/src/
sudo tar -xaf iperf-3.1.3-source.tar.gz -C /usr/local/src/

# 然后进入 iperf-3.1.3 目录:
cd iperf-3.1.3

现在开始构建程序,首先使用 configure 程序分析构建环境(buildenvironment),这是源代码树提供的一个 Shell 脚本,多数源代码设计成可移植,在多种类 Unix 系统上构建时,略微调整源代码就可以适应系统之间的差异,它还会检查系统是否已经安装了必要的外部工具和组件。

# 可以先查看帮助及配置选项
./configure --help | less

sudo ./configure

./configure 将会例行检查,如没有安装编译器及需要的使用程序就会看到:

checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... no
checking whether make supports nested variables... no
checking whether to enable maintainer-specific portions of Makefiles... no
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for gcc... no
checking for cc... no
checking for cl.exe... no
configure: error: in `/usr/local/src/iperf-3.1.3':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details

另外 ./configure 还可以搭配一些可选配置选项,如最常用的 --prefix=<路径名> 用来指定软件的安装位置,这些选项都可在 ./configure --help 中查看:

sudo ./configure --prefix=/usr/local/bin/

💡 iPerf3 默认就装在 /usr/local/bin/ 所以此处就不指定了。

如上看到输出结果没有报错,configure 会在源码目录内生成一些文件,其中最重要的一个文件就是 Makefile

Makefile 是指导 make 命令如何构建可执行程序的配置文件,由于是文本文件,如果你想要查看可以使用命令:less Makefile

sudo make

make 阶段如果出现错误,就按照报错信息进行解决,然后在使用 make clean 后再次 make

最后安装:

sudo make install

安装完成后可以查看所安装程序的版本以检查是否安装成功:

# -v 等同于 --version 用于查看软件的版本号,大部分软件都有该参数
iperf -v
# 或
iperf3 -v
iperf 3.1.3

能看到版本号就表示安装成功了,如果是别的错误信息就根据错误信息进行排错,如 iperf3 可能会遇到:

iperf3: error while loading shared libraries: libiperf.so.0: cannot open shared object file: No such file or directory

按照官网的解决方案:

sudo wget -O /usr/lib/libiperf.so.0 https://iperf.fr/download/ubuntu/libiperf.so.0_3.1.3

报错信息很重要,通过报错信息解决问题也是一个良好习惯,如果你不知道报错信息是什么意思就复制它到搜索引擎搜一下吧。

另外,在实际安装不同软件时最好查看软件的官方使用说明,比如开源在 Github 的软件都会有一份名为 README.md 说明文件指导使用,一些软件可能还需要一些第三方依赖库,阅读使用说明是一个良好习惯并因此可少走不少弯路。

软件包管理器 vs 源码编译

不要优先使用编译安装

不知道是受什么教材的影响,国内以 CentOS 的用户为代表的群体(甚至一些从业人员)非常痴迷于采用编译安装的方式安装软件,甚至误导了更多的人非编译安装不可,认为编译安装更能体验专业性...

但实际上这根本体现不了什么,简直是一股歪风邪气。

什么时候使用源码编译

使用软件包管理器的缺点很明显,软件仓库提供什么就使用,那么假设软件仓库没有提供你所需要的软件,或是你对你所需要的软件有版本要求。

例如软件的最新版有一个特性很吸引你,但是软件仓库中的版本还较为落后不知道什么时候更新到所需要的版本。再或者像 NGINX 那样一些模块没有办法动态加载需要编译到 NGINX 的二进制文件中。

也就是说,优先使用包管理器管理软件,当包管理器无法满足需求时使用源码编译的方式安装软件。

因为在使用源码编译时,软件包管理器无法得知你的操作,也就没办法接管并升级或卸载所编译的软件。

此外,在编译安装时可能需要处理复杂的依赖关系以及对系统造成「污染」。特别是依赖,十天半个月后还记得曾经如何解决以及都有哪些依赖关系吗?当这些依赖出现安全漏洞需要及时更新呢?