Package Management and Processing

Linux的软件包管理系统

Linux发行版本质量最重要的决定因素是软件包管理系统和其支持社区的持久性。其中,软件包管理系统即Linux的打包系统,它负责从其支持的社区,即资源库(类似于手机上的软件商店)中搜索用户想要的软件包,并替用户完成从软件包到软件的安装。软件包管理系统通常由两种工具类型组成:

  • 安装和删除软件包文件的底层工具
  • 在资源库中完成元数据、软件包搜索和依赖解析(软件很少是独立运行的,其运行过程中需要用到的其他软件就是它的依赖程序,某一软件包安装的同时也要确保依赖程序也被正确安装)的上层工具,其作用类似于Python的pipconda

不同的Linux发行版本使用的软件包管理系统不尽相同,但是主流的基本都属于Debian的.deb或Red Hat的.rpm阵营:

包管理系统 底层工具 上层工具 发行版本
Debian Style(.deb) dpkg apt, apt-get, apt-cache, aptitude Ubuntu, Debian, Xandros, Linspire
Red Hat Style (.rpm) rpm yum CentOS, Fedora, Red Hat Enterprise Linux, OpenSUSE, Mandriva, PCLinuxOS

Debian Style

当前的主流Linux发行版本,如Ubuntu、Debian,使用的都是Debian Style的包管理系统。相比于Red Hat Style的包管理系统,Debian Style的上层工具要更加多样,但也相应更加复杂。apt-getapt-cache是最早的支持Debian包管理系统的上层工具;apt包含了apt-getapt-cache中最常用命令选项的集合,但是apt本身要更加简洁;aptitude功能更加全面,且在搜索上的表现优于apt。一些常用的指令有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查找资源库软件包
apt-cache search [search_string] 或 apt search [search_string]
# 从资源库中安装一个软件包
apt-get install [package_name] 或 apt install [package_name]
# 卸载软件
apt-get remove [package_name] 或 apt remove [package_name]
# 自动删除不需要的包
apt-get autoremove 或 apt autoremove
# 经过资源库升级所有可升级的软件包
apt-get upgrade 或 apt upgrade
# 在升级软件包的时候自动处理依赖关系
apt-get dist-upgrade 或 apt full-upgrade
# 显示所安装软件包的说明信息
apt-cache show [package_name] 或 apt show [package_name]

事实上,在查找search安装install更新upgrade软件包时,上层工具都会去搜索软件包列表,软件包列表里包含了各类软件包的信息。但是,软件包列表会随时间变化,如添加新的包、删除旧的包等都会导致列表发生变化,而上层工具搜索的往往是软件包列表的缓存,因此,在进行前面的3项操作前,应先用apt-get updateapt update刷新软件包列表缓存。

search所用到的[search_string]可以是软件包的名字,也可以是软件包的说明信息。

aptitude的指令名字与使用方式和其他3个基本一致。

通常,以指令调用上层工具后,上层工具会自动调用下层工具帮助我们完成后续的安装任务,不过有时我们可能会直接得到软件包.deb文件,这时我们就可以直接使用底层工具来处理它们:

1
2
3
4
5
6
7
8
9
10
# 通过软件包安装软件
dpkg --install [package_file]
# 通过软件包升级软件
dpkg --install [package_file]
# 列出所有安装的软件包
dpkg --list
# 确定是否安装了某个软件
dpkg --status [package_name]
# 查找安装了指定文件的软件包
dpkg --search file_name

需要注意的是,直接通过软件包安装文件时,底层工具不会帮助我们完成依赖解析,因此若其发现缺少了某个依赖程序,它会直接报错并退出。一旦发生这种情况,我们就需要手动地安装依赖程序。

apt的全称是Advanced Packaging Tool,而dpkg的全称是Debian Packager。

Red Hat Style

Red Hat Style的上层工具只有yum

1
2
3
4
5
6
7
8
9
10
# 查找资源库软件包
yum search [search_string]
# 从资源库中安装一个软件包
yum install [package_name]
# 卸载软件
yum erase [package_name]
# 经过资源库升级所有可升级的软件包
yum update
# 显示所安装软件包的说明信息
yum info show [package_name]

同样地,Red Hat Style也支持用底层工具直接安装.rpm文件:

1
2
3
4
5
6
7
8
9
10
# 通过软件包安装软件
rpm -i [package_file]
# 通过软件包升级软件
rpm -U [package_file]
# 列出所有安装的软件包
rpm -qa
# 确定是否安装了某个软件
rpm -q [package_name]
# 查找安装了指定文件的软件包
rpm -qf file_name

使用rpm直接安装软件包同样需要我们自己处理依赖程序。

yum的全称是Yellow dog Updater, Modified,rpm的全称是Red Hat Package Manager。

压缩,归档,备份

压缩包、文件包是另外两种常见的“包文件”。不像软件包是用于装载某个可执行程序的,它们是用于更好地存储、管理、归档和备份文件以保护重要数据的。

压缩文件

压缩(Zip、Compress),顾名思义就是通过压缩算法,使得计算机能以尽可能小的空间来存储未压缩文件。压缩算法分两大类:

  • 无损压缩:无损压缩保留了原文件的所有数据。当还原一个被压缩文件时,还原文件将与原文件一模一样;
  • 有损压缩:有损压缩允许压缩操作时删除一些数据,使得文件得到更大的压缩率。当一个有损压缩文件被还原时,它与原文件将存在差异。有损压缩一般用于少量的损失不影响原文件效果的文件,如图片、视频等。JPEG和MP3就是常见的有损压缩文件。

gzip:强大的无损压缩程序

gzip,全称GNUzip,是GNU计划实现的一款Linux系统常用的无损压缩程序。gunzip则是其对应的解压程序。

1
2
gzip [OPTION]... [FILE]...
gunzip [OPTION]... [FILE]...

gzip在执行压缩操作时,原文件会被压缩文件[FILE].gz替代;解压时,[FILE].gz又会被还原为原文件:

1
2
3
4
5
6
7
8
9
[meme@localhost Playground]$ ls -l /etc | cat > foo.txt
[meme@localhost Playground]$ ls -l foo.*
--w-rw-rw-. 1 meme meme 16387 Jul 31 00:45 foo.txt
[meme@localhost Playground]$ sudo gzip foo.txt
[meme@localhost Playground]$ ls -l foo.*
--w-rw-rw-. 1 meme meme 3219 Jul 31 00:45 foo.txt.gz
[meme@localhost Playground]$ sudo gunzip foo.txt.gz
[meme@localhost Playground]$ ls -l foo.*
--w-rw-rw-. 1 meme meme 16387 Jul 31 00:45 foo.txt

不难看出,压缩后的[FILE].gz文件大小约为原文件的1/5。解压后的文件与原文件一模一样,包括权限、修改时间等。gzipgunzip[OPTION]选项有很多,常用的有:

  • -c:把程序输出写入到标准输出,并且保留原始文件。用在gzip上会使得此次gzip操作将压缩后的文件内容(一堆乱码)直接输出在标准输出上,而原文件不发生变化:
    1
    2
    3
    4
    5
    6
    7
    [meme@localhost Playground]$ ls
    dir foo.txt
    [meme@localhost Playground]$ sudo gzip -c foo.txt
    �f�d
    ...
    [meme@localhost Playground]$ ls
    dir foo.txt
    用在gunzip上会使得gunzip直接输出解压后文件的内容,而并不解压文件:
    1
    2
    3
    4
    5
    6
    7
    8
    [meme@localhost Playground]$ sudo gzip foo.txt
    [meme@localhost Playground]$ ls
    dir foo.txt.gz
    [meme@localhost Playground]$ sudo gunzip -c foo.txt.gz
    total 1424
    ...
    [meme@localhost Playground]$ ls
    dir foo.txt.gz
    zcat(zip & cat)命令相当于使用了-cgunzip
  • -d:意为decompress,只用于gzip,带-d选项的gzip作用相当于gunzip
  • -f:意为force,指强制压缩原文件,即便原始文件已经被压缩。
  • -r:意为recursive,若命令行中有一个或多个参数为目录,则递归地压缩目录中文件。

bzip2:慢但更彻底的gzip

bzip2gzip相似,但是使用了不同的压缩算法,使得bzip2可以做到更高的压缩级别,但速度就稍微慢了一点。除了bzip2的压缩文件名字后缀为.bz2bzip2gzip的功能几乎一模一样,包括压缩文件替代原文件、除-r以外的选项以及bunzip2bzcat完成解压缩。

归档文件

相比于压缩文件包,我们更常见的是普通的文件包,即归档文件包,Windows上的.zip文件包就是一种常见的归档文件包。所谓归档(Archive),即把一群文件捆绑成一个大的文件的过程。归档通常只是将目标文件群集中地放在一个归档文件包中,但有些归档程序,如zip,还包含着压缩的功能。

tar:经典Linux归档工具

tar,全称Tape Archive,是Linux系统中最常用的归档工具。一个tar包可以由一组独立的文件、一个或者多个目录或前两者的混合体组成。

1
tar MODE[OPTION...] [PATH]...

上边的MODEtar的不同操作模式,常用的有:

  • c:为文件、目录、目录列表创建归档文件;
  • x:抽取归档文件;
  • r:追加具体的路径到归档文件末尾;
  • t:列出归档文件的内容。

而常用的[OPTION]有:

  • -f:意为file,指定归档文件包的名字;
  • -v:意为verbose,显示命令的整个执行过程;
  • -z:意为gzip,对归档文件包使用gzip压缩;
  • -j:意为bip2,对归档文件包使用bzip2压缩。

一般来说,模式只能选择一个,而选项可以选择多个。确定了模式后,选项可以直接跟在模式后面,而无需用空格或-分割。

tar包的后缀名为.tar,经过gzip压缩的tar包后缀名为.tgz,经过bzip2压缩的tar包后缀名为.tbz

在进一步了解tar之前,我们先在当前目录下创建playground目录,并在其下创建100个子目录,在每个子目录下创建24个普通文件:

1
2
3
4
5
6
7
8
[meme@localhost Playground]$ mkdir -p ./playground/dir-{00{1..9},0{10..99},100}
[meme@localhost Playground]$ ls ./playground
dir-001 ... dir-100
[meme@localhost Playground]$ touch ./playground/dir-{00{1..9},0{10..99},100}/file-{A..Z}
[meme@localhost Playground]$ ls ./playground/dir-001
file-A ... file-Z
[meme@localhost Playground]$ ls ./playgrounddir-002
file-A ... file-Z

tar一个很重要的特性是它在归档时保留了原文档的目录结构。如,若我们在当前目录下归档playground文件夹:

1
2
3
4
5
[meme@localhost Playground]$ ls
foo.txt.gz playground
[meme@localhost Playground]$ tar cf playground.tar playground
[meme@localhost Playground]$ ls
foo.txt.gz playground playground.tar

使用tar tf可以列出归档文件的内容,tar tvf可以列出归档文件的详细内容:

1
2
3
4
5
6
7
8
9
10
11
12
[meme@localhost Playground]$ tar tf playground.tar playground
playground/
playground/dir-001/
playground/dir-001/file-A
playground/dir-001/file-B
...
[meme@localhost Playground]$ tar tvf playground.tar playground
drwxrwxr-x meme/meme 0 2023-07-31 01:25 playground/
drwxrwxr-x meme/meme 0 2023-07-31 01:30 playground/dir-001/
-rw-rw-r-- meme/meme 0 2023-07-31 01:28 playground/dir-001/file-A
-rw-rw-r-- meme/meme 0 2023-07-31 01:28 playground/dir-001/file-B
...

若我们使用同样的命令,但是将待归档文件夹的路径更改为其绝对路径,则其归档文件的结构也将是绝对路径的结构:

1
2
3
4
5
[meme@localhost Playground]$ pwd
/home/meme/Playground
[meme@localhost Playground]$ tar cf playground2.tar /home/meme/Playground/playground
[meme@localhost Playground]$ ls
foo.txt.gz playground playground2.tar playground.tar

我们不妨将playground.tarplayground2.tartar xf同时提取在新建文件夹foo中:

1
2
3
4
5
6
7
8
9
10
11
12
[meme@localhost Playground]$ mkdir foo
[meme@localhost Playground]$ cd foo
[meme@localhost foo]$ tar xf ../playground.tar
[meme@localhost foo]$ tar xf ../playground2.tar
[meme@localhost foo]$ ls ./playground
dir-001
...
dir-100
[meme@localhost foo]$ ls ./home/meme/Playground/playground
dir-001
...
dir-100

可见,playground.tarplayground2.tar内部的内容分别按相对路径和绝对路径的目录结构存储。对playground.tar,是playground/;对playground2.tar则是home/meme/Playground/playground/。这样的设计可以保证我们归档目录结构与原目录结构的完全统一,使得我们可以在任何地方抽取归档文件。比如,playground2.tar就可以完全在另一台主机的根目录下抽取,使得其在新主机的路径仍为home/meme/Playground/playground

需要注意的是,tar xf默认抽取所有文件,但是若其后面有指定的文件路径,则其只会抽取指定文件,如,此处我们先删除home,再只从playground2.tar中抽取一个目录dir-001

1
2
3
4
5
6
7
8
9
10
[meme@localhost foo]$ ls
home playground
[meme@localhost foo]$ rm -r home
[meme@localhost foo]$ ls
playground
[meme@localhost foo]$ tar xf ../playground2.tar home/meme/Playground/playground/dir-001
[meme@localhost foo]$ ls
home playground
[meme@localhost foo]$ ls ./home/meme/Playground/playground
dir-001

在有些版本的Linux发行版中,在命令的.tar文件名后加入--wildcards可以使得我们的路径能支持通配符,但是路径最好用引号引起。

已抽出文件不会继承打包文件的权限,其只拥有将其抽出的用户的权限,也就是说,抽出文件的拥有者是抽取人,除非打包文件的是根用户。

find的结合

由于find搜索指定文件夹中的文件时给出的搜索结果是相对于该文件夹的相对路径,因此tar常常可以与find结合使用,使得find的搜索结果可以作为tar的待归档文件,并用tar rf直接在原归档文件的基础上增加新内容,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[meme@localhost Playground]$ find playground -name 'file-A'
playground/dir-001/file-A
playground/dir-002/file-A
...
playground/dir-099/file-A
playground/dir-100/file-A
[meme@localhost Playground]$ find playground -name 'file-A' -exec tar rf playground.tar '{}' '+'
[meme@localhost Playground]$ tar tf playground.tar
playground/
playground/dir-001/
playground/dir-001/file-A
playground/dir-001/file-B
...
playground/dir-100/file-Z
playground/dir-001/file-A
playground/dir-002/file-A
...
playground/dir-099/file-A
playground/dir-100/file-A

将直接将所有的file-A文件归档、增加至playground.tar中。

与标准输入和输出结合

通常地,tar cf [FILE.tar] [PATH]不会产生输出,而是直接生成FILE.tar文件,但是,若我们将[FILE.tar]替换为-,则上述指令会将归档文件包以标准输出的形式输出;同样地,tar xf [FILE.tar]不会接受输入,只会生成提取文件,而我们若将[FILE.tar]替换为-,则tar xf接受标准输入的输入

1
2
3
4
5
6
7
8
[meme@localhost foo]$ ls
home playground
[meme@localhost foo]$ rm -r playground
[meme@localhost foo]$ ls
home
[meme@localhost foo]$ tar cf - ../playground | tar xf -
[meme@localhost foo]$ ls
home playground

上述命令将tar cf../playground的归档输出到-标准输出中,该标准输出又被管道至tar xf-标准输入,tar xf-提取,于是我们就在当前文件夹得到父文件夹的playground文件夹。

-代替标准输入、输出的惯例被很多程序使用。

由此,前面findtar的结合也可以通过管道|进行:

1
2
3
4
5
6
7
8
9
10
11
[meme@localhost Playground]$ ls
foo foo.txt.gz playground playground2.tar
[meme@localhost Playground]$ find playground -name 'file-A' | tar cf playground.tar -T-
[meme@localhost Playground]$ ls
foo foo.txt.gz playground playground2.tar playground.tar
[meme@localhost Playground]$ tar tf playground.tar
playground/dir-001/file-A
playground/dir-002/file-A
...
playground/dir-099/file-A
playground/dir-100/file-A

-T全称--file-from,它将导致tar从一个文件(此处为标准输出文件)而不是命令行中读入路径名,-T后面的-示意该文件为管道过来的标准输入文件。最终整个命令达到的效果就是将playground中所有的file-A文件归档。

-T-可以用--file-from=-代替。

.tgz.tbz

tar czf [FILE.tgz] [PATH]tar cjf [FILE.tbz] [PATH]将分别把得到的.tar文件一步压缩为.tgz.tbz文件,此处不再赘述。

zip:打包与压缩

zip更常用于Windows系统中。相比于tarzip兼具打包和压缩的功能。

1
zip [OPTION] [ZIPFILE] [FILE]

zip的使用很简单,基本格式为包文件名+待打包文件路径,对于目录文件则要加上-r选项,否则只有目录被存储:

1
2
3
4
5
6
7
8
9
[meme@localhost Playground]$ ls
foo foo.txt.gz playground playground2.tar playground.tar
[meme@localhost Playground]$ zip -r dir-001.zip playground/dir-001
adding: playground/dir-001/ (stored 0%)
adding: playground/dir-001/file-A (stored 0%)
...
adding: playground/dir-001/file-Z (stored 0%)
[meme@localhost Playground]$ ls
dir-001.zip foo foo.txt.gz playground playground2.tar playground.tar

上述指令将playground中的dir-001文件夹打包压缩为dir-001.zip。值得注意的是,zip有压缩功能,stored后的百分比即表示压缩量。因为各个file-[A..Z]文件均为空文件,所以没有压缩。

zip的打包是更新式的打包而不是替代式的打包,也就是说,若已经存在了文件包xx.zip,再以相同的名字xx.zip打包某群文件不会再生成一个.zip文件,而是在原xx.zip文件包的基础上进行更新,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[meme@localhost Playground]$ zip -r dir-001.zip playground/dir-002
adding: playground/dir-002/ (stored 0%)
...
adding: playground/dir-002/file-Z (stored 0%)
[meme@localhost Playground]$ ls
dir-001.zip foo foo.txt.gz playground playground2.tar playground.tar
[meme@localhost Playground]$ unzip -l dir-001.zip
Archive: dir-001.zip
Length Date Time Name
--------- ---------- ----- ----
0 07-31-2023 01:30 playground/dir-001/
...
0 07-31-2023 01:28 playground/dir-001/file-Z
0 07-31-2023 01:30 playground/dir-002/
...
0 07-31-2023 01:28 playground/dir-002/file-Z
--------- -------
0 54 files

dir-002直接被加进了dir-001.zip中,类似的操作在tar中要指定r模式。

上面用到的unzip是与zip对应的解压程序。unzip -l [FILE.zip] [PATH]将只列出文件包内某文件的信息,[PATH]缺省则列出所有。而去掉-l则将从包中抽取所有或指定文件。

zip也能结合标准输入输出,此处不再赘述。

同步与备份文件

同步与备份,即保持一个或多个目录与另一个本地或远程目录保持同步。常见的git仓库就是一个这样的远程托管同步系统。在Linux中,更常被使用的同步备份工具是rsync,全称Remote Synchronize。

1
rsync [OPTION] SOURCE DESTINATION

其中SOURCE是待备份文件(可多个),而DESTINATION是即将存储SOURCE全部文件内容的文件夹,两者必须是下列3个选项之一且至少一个是本地文件/目录:

  • 一个本地文件/目录;
  • 一个远程文件/目录,以[user@]host:path的形式存在;
  • 一个远程rsync服务器,由rsync://[user@]host[:port]/path指定。

其中带[]的表示可选项,因此对于远程文件/目录,我们只要写上远程主机名host以及存放目录path;对于远程rsync服务器,我们只要写上远程服务器的主机名(url的形式)host及其存放目录path

实际使用时,我们一般会用rsync -ac -delete,其中-a表示递归地备份并保护文件属性,-v表示输出备份信息,-delete表示删除备份设备中已经存在但是不存在于源设备中的文件。若SOURCEDESTINATION中有一个为远程ssh主机,还要增加选项--rsh==ssh

参考