第 10 章 数据管理

以下是关于在 Debian 系统上管理二进制和文本数据的工具及其相关提示。

10.1. 共享,拷贝和存档

[警告]警告

为避免 竞争情况,不应当对正在进行写操作的设备和文件,多个进程进行不协调的写操作。采用flock(1) 的 文件锁定 机制可用于避免这种情况。

数据的安全和它的受控共享有如下几个方面。

  • 存档文件的建立

  • 远程存储访问

  • 复制

  • 跟踪修改历史

  • 促进数据共享

  • 防止未经授权的文件访问

  • 检测未经授权的文件修改

这些可以通过使用工具集来实现。

  • 存档和压缩工具

  • 复制和同步工具

  • 网络文件系统

  • 移动存储媒介

  • 安全 shell

  • 认证体系

  • 版本控制系统工具

  • 哈希算法和加密工具

10.1.1. 存档和压缩工具

以下是 Debian 系统上可用的存档和压缩工具的预览。

表 10.1. 存档和压缩工具列表

软件包流行度大小扩展名命令描述
tarV:905, I:9993098.tartar(1)标准的归档工具(默认)
cpioV:412, I:9981136.cpiocpio(1)Unix System V 风格的归档器,与 find(1) 一起使用
binutilsV:164, I:67897.arar(1)创建静态库的归档工具
fastjarV:2, I:29183.jarfastjar(1)Java 归档工具(类似 zip)
paxV:13, I:26170.paxpax(1)新的 POSIX 归档工具,介于 tarcpio 之间
gzipV:883, I:999245.gzgzip(1), zcat(1), …GNU LZ77 压缩工具(默认)
bzip2V:157, I:970122.bz2bzip2(1), bzcat(1), …Burrows-Wheeler block-sorting 压缩工具有着比 gzip(1) 更高的压缩率 (跟 gzip 有着相似的语法但速度比它慢)
lzmaV:2, I:29149.lzmalzma(1)LZMA 压缩工具有着比 gzip(1) 更高的压缩率(不推荐)
xz-utilsV:454, I:977612.xzxz(1), xzdec(1), …XZ 压缩工具有着比 bzip2(1) 更高的压缩率(压缩速度慢于 gzip 但是比 bzip2 快; LZMA 压缩工具的替代品)
p7zipV:89, I:464987.7z7zr(1), p7zip(1)有着更高压缩率的 7-zip 文件归档器(LZMA 压缩)
p7zip-fullV:113, I:4864664.7z7z(1), 7za(1)有着更高压缩率的 7-Zip 文件归档器(LZMA 压缩和其他)
lzopV:9, I:76164.lzolzop(1)LZO 压缩工具有着比 gzip(1) 更高的压缩和解压缩速度 (跟 gzip 有着相似的语法但压缩率比它低)
zipV:51, I:432608.zipzip(1)InfoZip:DOS 归档器和压缩工具
unzipV:154, I:798566.zipunzip(1)InfoZIP:DOS 解档器和解压缩工具
[警告]警告

除非你知道将会发生什么,否则不要设置 “$TAPE“ 变量。它会改变 tar(1) 的行为。

[注意]注意

gzipped tar(1) 归档器用于扩展名是 “.tgz“ 或者 “.tar.gz“ 的文件。

[注意]注意

xz-compressed tar(1) 归档器用于扩展名是 “.txz“ 或者 “.tar.xz“ 的文件。

[注意]注意

FOSS 工具,例如 tar(1),中的主流压缩方法已经按如下所示的迁移: gzipbzip2xz

[注意]注意

cp(1),scp(1) 和 tar(1) 工具可能并不适用于一些特殊的文件。cpio(1) 工具的适用范围是最广的。

[注意]注意

cpio(1) 是被设计为与 find(1) 和其它命令一起使用,适合于创建备份脚本的场景,因此,脚本的文件选择部分能够被独立测试。

[注意]注意

Libreoffice 数据文件的内部结构是 “.jar“ 文件,它也可以使用 unzip 工具来打开。

[注意]注意

事实上跨平台支持最好的存档工具是 zip。按照“zip -rX”的方式调用可以获得最大的兼容性。如果最大文件大小需要纳入考虑范围,请同时配合“-s”选项使用。

10.1.2. 复制和同步工具

以下是 Debian 系统上的可用的简单复制和备份工具的预览。

表 10.2. 复制和同步工具列表

软件包流行度大小工具功能
coreutilsV:891, I:99917478GNU cp复制本地文件和目录(“-a” 参数实现递归)
openssh-clientV:803, I:9964298scp复制远端文件和目录(客户端,”-r“ 参数实现递归)
openssh-serverV:690, I:8341567sshd复制远端文件和目录(远程服务器)
rsyncV:281, I:560677-单向远程同步和备份
unisonV:4, I:1714-双向远程同步和备份

在复制文件的时候, rsync(8) 比其他工具提供了更多的特性。

  • 差分传输算法只会发送源文件与已存在的目标文件之间的差异部分

  • 快速检查算法 (默认) 会查找大小或者最后的修改时间有变化的文件

  • --exclude“ 和 “--exclude-from“ 选项类似于 tar(1)

  • 在源目录中添加反斜杠的语法能够避免在目标文件中创建额外的目录级别。

[提示]提示

在 cron(8) 下使用”-gl“选项执行在第 10.2.3 节 “用于备份数据的复制脚本”里提到的 bkup 脚本,将提供一个和 Plan9(译注:Plan9 也是一种文件系统,又称 v9fs, 可以用 apt-cache show 9mount 命令获取相关信息)的 dumpfs 静态数据归档非常相似的功能。

[提示]提示

表 10.11 “版本控制系统工具列表” 中的版本控制系统 (VCS) 可以被认为是多路拷贝和同步工具。

10.1.3. 归档语法

以下是用不同的工具压缩和解压缩整个 “./source“ 目录中的内容。

GNU tar(1):

  1. $ tar -cvJf archive.tar.xz ./source
  2. $ tar -xvJf archive.tar.xz

或者,如下所示。

  1. $ find ./source -xdev -print0 | tar -cvJf archive.tar.xz --null -F -

cpio(1):

  1. $ find ./source -xdev -print0 | cpio -ov --null > archive.cpio; xz archive.cpio
  2. $ zcat archive.cpio.xz | cpio -i

10.1.4. 复制语法

如下是用不同的工具复制整个 “./source“ 目录中的内容。

  • 本地复制: “./source“ 目录 → “/dest“ 目录

  • 远程复制:本地主机上的 “./source" 目录 → “user@host.dom“ 主机上的 “/dest“ 目录

rsync(8):

  1. # cd ./source; rsync -aHAXSv . /dest
  2. # cd ./source; rsync -aHAXSv . user@host.dom:/dest

你能够选择使用“源目录上的反斜杠”语法。

  1. # rsync -aHAXSv ./source/ /dest
  2. # rsync -aHAXSv ./source/ user@host.dom:/dest

或者,如下所示。

  1. # cd ./source; find . -print0 | rsync -aHAXSv0 --files-from=- . /dest
  2. # cd ./source; find . -print0 | rsync -aHAXSv0 --files-from=- . user@host.dom:/dest

GNU cp(1) 和 openSSH scp(1):

  1. # cd ./source; cp -a . /dest
  2. # cd ./source; scp -pr . user@host.dom:/dest

GNU tar(1):

  1. # (cd ./source && tar cf - . ) | (cd /dest && tar xvfp - )
  2. # (cd ./source && tar cf - . ) | ssh user@host.dom '(cd /dest && tar xvfp - )'

cpio(1):

  1. # cd ./source; find . -print0 | cpio -pvdm --null --sparse /dest

你能够在所有包含 “.“ 的例子里用 “foo“ 替代 “.“,这样就可以从 “./source/foo“ 目录复制文件到 “/dest/foo“ 目录。

在所有包含 “.“ 的列子里,你能够使用绝对路径 “/path/to/source/foo“ 来代替 “.“,这样可以去掉 “cd ./source;“. 如下所示,这些文件会根据工具的不同,拷贝到不同的位置。

  • /dest/foo“: rsync(8), GNU cp(1), 和 scp(1)

  • /dest/path/to/source/foo“: GNU tar(1), 和 cpio(1)

[提示]提示

rsync(8) 和 GNU cp(1) 可以用 “-u“ 选项来忽略接受端上更新的文件。

10.1.5. 查找文件的语法

find(1) 被用作从归档中筛选文件也被用作拷贝命令 (参见第 10.1.3 节 “归档语法”第 10.1.4 节 “复制语法”) 或者用于 xargs(1) (参见第 9.3.9 节 “使用文件循环来重复一个命令”)。通过 find 的命令行参数能够使其功能得到加强。

以下是 find(1)基本语法的总结。

  • find 条件参数的运算规则是从左到右。

  • 一旦输出是确定的,那么运算就会停止。

  • “逻辑 OR“ (由条件之间的 “-o“ 参数指定的)优先级低于 “逻辑 AND“ (由 “-a“ 参数指定或者条件之间没有任何参数)。

  • ”逻辑 NOT“ (由条件前面的 “!“ 指定) 优先级高于 “逻辑 AND”。

  • -prune“ 总是返回逻辑 TRUE 并且如果这个目录是存在的,将会搜索除这个目录以外的文件。

  • -name“ 选项匹配带有 shell 通配符 (参见第 1.5.6 节 “Shell 通配符”) 的文件名但也匹配带有类似 “*“ 和 “?“ 元字符的 .“。(新的 POSIX 特性)

  • -regex“ 匹配整个文件路径,默认采用 emacs 风格的 BRE (参见第 1.6.2 节 “正则表达式”)。

  • -size“ 根据文件大小来匹配 (值前面带有 “+“ 号匹配更大的文件,值前面带有 “-“ 号匹配更小的文件)

  • -newer“ 参数匹配比参数名中指定的文件还要新的文件。

  • -print0“ 参数总是返回逻辑 TRUE 并将完整文件名 (null terminated) 打印到标准输出设备上。

如下是 find(1) 语法格式。

  1. # find /path/to \
  2. -xdev -regextype posix-extended \
  3. -type f -regex ".*\.cpio|.*~" -prune -o \
  4. -type d -regex ".*/\.git" -prune -o \
  5. -type f -size +99M -prune -o \
  6. -type f -newer /path/to/timestamp -print0

这些命令会执行如下动作。

  1. 查找 “/path/to“ 下的所有文件

  2. 限定全局查找的文件系统并且使用的是 ERE (参见第 1.6.2 节 “正则表达式”)

  3. 通过停止处理的方式来排除匹配 “.*\.cpio“ 或 “.*~“ 正则表达式的文件

  4. 通过停止处理的方式来排除匹配 “.*/\.git“ 正则表达式的目录

  5. 通过停止处理的方式来排除比 99MB (1048576字节单元) 更大的文件

  6. 显示文件名,满足以上搜索条件并且比 “/path/to/timestamp“ 新的文件

请留心以上例子中的 “-prune -o“ 排除文件的习惯用法。

[注意]注意

对于非 Debian 系的 Unix-like 系统,有些参数可能不被 find(1) 命令所支持。在这种情况下,应该考虑调整匹配方法并用 “-print“ 替代 “-print0“。你可能同样需要更改其他相关的命令。

10.1.6. 归档媒体

为重要的数据存档寻找 存储设备 时,你应该注意它们的局限性。对于小型的个人数据备份,我使用品牌公司的 CD-R 和 DVD-R 然后把它放在阴凉、干燥、清洁的地方。(专业的一般使用磁带存档介质)

[注意]注意

防火安全是对于纸质文档来说的,大多数的计算机数据存储媒介耐热性比纸差。我经常依赖存储在多个安全地点的加密拷贝。

网上(主要是来源于供应商信息)可以查看存储介质的最大使用寿命。

  • 大于100年:用墨水的无酸纸

  • 100年:光盘存储(CD/DVD,CD/DVD-R)

  • 30年:磁带存储(磁带,软盘)

  • 20年:相变光盘存储(CD-RW)

这不包括由于人为导致的机械故障等等。

网上(主要来源于供应商信息)可以查看存储介质的最大的写次数。

  • 大于250,000次:硬盘驱动器

  • 大于10,000次:闪存

  • 1,000次:CD/DVD-RW

  • 1次:CD/DVD-R,纸

[小心]小心

这里的存储寿命和写次数的数据不应该被用来决定任何用于关键数据的存储媒介,请翻阅制造商提供的特定产品的说明。

[提示]提示

因为 CD/DVD-R 和 纸只能写一次,它们从根本上阻止了因为重写导致的数据意外丢失。这是优点!

[提示]提示

如果你需要更快更频繁的进行大数据备份,那么通过高速网络连接的远端主机上的硬盘来实现备份,可能是唯一可行的方法。

10.1.7. 可移动存储设备

可移动存储设备可能是以下的任何一种。

它们可以通过以下的方式来进行连接。

像 GNOME 和 KDE 这样的现代桌面环境能够在 “/etc/fstab“ 文件中没有匹配条目的时候,自动挂载这些可移动设备。

  • udisks 包提供了守护进程和相关的实用程序来挂载和卸载这些设备。

  • D-bus 创建事件来触发自动处理。

  • PolicyKit 提供了所需的特权。

[提示]提示

umount(8) 在自动挂载设备的时候可能会带有 “uhelper=“ 参数。

[提示]提示

只有当这些可移动设备没有在 “/etc/fstab“ 文件中列出时,桌面环境下才会自动挂载。

现代桌面环境下的挂载点被选为 “/media/<disk_label>“,它可以被如下所示的来定制。

  • FAT 格式的文件系统使用 mlabel(1) 命令

  • ISO9660 文件系统使用带有 “-V“ 选项的 genisoimage(1) 命令

  • ext2/ext3/ext4 文件系统使用带有 “-L“ 选项的 tune2fs(1) 命令

[提示]提示

挂载时可能需要提供编码选项(参见 第 8.4.6 节 “文件名编码”)。

[提示]提示

在图形界面菜单上移除文件系统,可能会移除它的动态设备节点例如 “/dev/sdc“。如果你想要保留它的设备节点,你应该在命令行提示符上输入 umount(8) 命令来卸载它。

10.1.8. 选择用于分享数据的文件系统

当你通过可移动存储设备与其他系统分享数据的时候,你应该先把它格式化为被两种操作系统都支持的通用的 文件系统。下面是文件系统的列表。

表 10.3. 典型使用场景下可移动存储设备可选择的文件系统列表

文件系统典型使用场景描述
FAT12软盘(<32MiB)上跨平台的数据分享
FAT16在小硬盘(<2GiB)上的跨平台的数据分享
FAT32在大硬盘(<8TiB,被 MS Windows95 OSR2 以上的操作系统所支持) 上的跨平台的数据分享
NTFS在大硬盘类设备上的跨平台共享数据 (在 MS Windows NT 和后续版本原生支持;在 Linux 上,通过使用 FUSENTFS-3G 支持。)
ISO9660在 CD-R 和 DVD+/-R 上的跨平台的静态数据分享
UDFCD-R 和 DVD+/-R (新)上的增量数据写入
MINIX 文件系统软盘上磁盘空间高利用率的 unix 文件数据存储
ext2 文件系统在装有老旧 linux 系统的硬盘上的数据分享
ext3 文件系统在装有老旧 linux 系统的硬盘上的数据分享
ext4 文件系统在装有较新的 linux 系统的硬盘上的数据分享
[提示]提示

查看第 9.8.1 节 “使用 dm-crypt/LUKS 加密移动磁盘”来获得关于使用设备级加密的跨平台的数据共享的信息。

FAT 文件系统被绝大多数的现代操作系统支持,它对于通过可移动硬盘进行的数据交换是非常有用的。

当格式化像装有 FAT 文件系统的跨平台数据共享的可移动设备时,以下应该是保险的选择。

  • 用 fdisk(8),cfdisk(8) 或者 parted(8) 命令(参见第 9.5.2 节 “硬盘分区配置”)把它们格式化为单个的主分区并对把它做如下标记。

    • 标记小于 2GB 的 FAT 设备为 字符”6”。

    • 标记更大的 FAT32 设备为字符 “c”。

  • 如下所示是用 mkfs.vfat(8) 命令格式化主分区的。

    • 它的设备名字,例如 “/dev/sda1“ 用于 FAT16 设备

    • 明确的选项和它的设备名,例如 “-F 32 /dev/sda1“ 用于 FAT32 设备

当使用 FAT 或 ISO9660 文件系统分享数据时,如下是需要注意的安全事项。

  • 用 tar(1),或cpio(1)命令压缩文件,目地是为了保留文件名,符号链接,原始的文件权限和文件所有者信息。

  • 用 split(1) 命令把压缩文件分解成若干小于 2GiB的小文件,使其免受文件大小限制。

  • 加密压缩文件保护其内容免受未经授权的访问。

[注意]注意

因为 FAT 文件系统的设计,最大的文件大小为 (2^32 - 1) bytes = (4GiB -1 byte)。对于一些老旧的 32 位系统上的应用程序而言,最大的文件大小甚至更小(2^31 -1) bytes = (2GiB -1 byte)。Debian 没有遇到后者的问题。

[注意]注意

微软系统本身并不建议在超过 200MB 的分区或者驱动器上使用 FAT。他们的 “ Overview of FAT, HPFS, and NTFS File Systems 这篇文章突出显示了微软系统的缺点,例如低效的磁盘空间利用。当然了,我们在 Linux 系统上还是应该使用 ext4 文件系统。

[提示]提示

有关文件系统和访问文件系统的更多信息,请参考 “Filesystems HOWTO“。

10.1.9. 网络上的数据分享

当使用网络来分享数据的时候,你应该使用通用的服务。这里有一些提示。

表 10.4. 典型使用场景下可选择的网络服务列表

网络服务典型使用场景描述
SMB/CIFSSamba 挂载网络文件系统通过 “Microsoft Windows 网络” 分享文件,参见 smb.conf(5) 和 官方 Samba 3.x.x 指导和参考手册(The Official Samba 3.x.x HOWTO and Reference Guide)samba-doc 软件包
NFS 用 Linux 内核挂载网络文件系统通过 “Unix/Linux 网络” 分享文件,参见 exports(5) 和 Linux NFS-HOWTO
HTTP 服务在 web 服务器/客户端之间分享文件
HTTPS 服务在有加密的安全套接层 (SSL) 或者安全传输层 (TLS) 的网络服务器/客户端中分享文件
FTP 服务在 FTP 服务器/客户端之间分享文件

尽管对于文件分享来说,通过网络挂载文件系统和传输文件是相当方便的,但这可能是不安全的。它们的网络连接必须通过如下所示的加强安全性。

  • SSL/TLS 加密

  • 建立 SSH 通道

  • 建立 VPN 通道

  • 网络之间需要有安全的防火墙

参见 第 6.10 节 “其它网络应用服务”第 6.11 节 “其它网络应用客户端”

10.2. 备份和恢复

我们都熟知计算机有时会出问题,或者由于人为的错误导致系统和数据损坏。备份和恢复操作是成功的系统管理中非常重要的一部分。可能有一天你的电脑就会出问题。

[提示]提示

保持你的备份系统简洁并且经常备份你的系统,有备份数据比你采用的备份方法的技术先进要重要的多。

有3个关键的因素决定实际的备份和恢复策略。

  1. 知道要备份和恢复什么。

    • 你自己创建的数据文件:在 “~/“ 下的数据

    • 你使用的应用程序创建的数据文件:在 “/var/“ 下的数据(除了 “/var/cache/“,”/var/run/“ 和 “/var/tmp/“)

    • 系统配置文件:在 “/etc/” 下的数据

    • 本地软件:在 “/usr/local/“ 或 “/opt/“ 下的数据

    • 系统安装信息:关键步骤 (分区,…) 的纯文本备忘录

    • 验证数据结果:通过实验性的恢复操作来预先验证

  2. 知道怎样去备份和恢复。

    • 安全的数据存储:保护其免于覆盖和系统故障

    • 经常备份:有计划的备份

    • 冗余备份:数据镜像

    • 傻瓜式操作:单个简单命令备份

  3. 评估涉及的风险和成本。

    • 评估数据丢失的损失

    • 备份所需的资源:人力,硬件,软件,…

    • 数据丢失的方式及其可能性

[注意]注意

除非你知道自己做的是什么,否则不要备份 /proc, /sys, /tmp, 和 /run 目录下的伪文件系统(参见 第 1.2.12 节 “procfs 和 sysfs”第 1.2.13 节 “tmpfs”)。它们是庞大且无用的数据。

至于安全的数据存储,数据至少是应该在不同的磁盘分区上最好是在不同的磁盘和机器上,来承受文件系统发生的损坏。重要的数据最好存储在只能写一次的媒介上例如 CD/DVD-R 来防止覆盖事故。(参见 第 9.7 节 “二进制数据” 怎样在 shell 命令行写入存储媒介。GNOME 桌面图形环境可以让你轻松的通过菜单:“位置 → CD/DVD 刻录”来实现写入操作。)

[注意]注意

当备份数据的时候,你可能希望停止一些应用程序的守护进程例如 MTA(参见第 6.3 节 “邮件传输代理 (MTA)”)。

[注意]注意

你应该格外小心地备份和恢复身份认证相关的数据文件例如 “/etc/ssh/ssh_host_dsa_key“, “/etc/ssh/ssh_host_rsa_key“, “~/.gnupg/“, “~/.ssh/“, /etc/passwd“, “/etc/shadow“, “/etc/fetchmailrc“, “popularity-contest.conf“, “/etc/ppp/pap-secrets“ 和 “/etc/exim4/passwd.client/“。 这些数据中的有一些文件是不能通过向系统输入同样的字符串来再生的。

[注意]注意

如果你以用户进程的方式执行 cron job,你必须存储文件到 “/var/spool/cron/crontabs“ 目录并且重启 cron(8)。参见第 9.3.14 节 “定时任务安排”来获得关于 cron(8) 和 crontab(1) 的信息。

10.2.1. 实用备份套件

以下是 Debian 系统上值得注意的实用备份程序套件的列表。

表 10.5. 实用备份程序套件列表

软件包流行度大小说明
dumpV:1, I:63524.4 BSD dump(8) 和 restore(8) 命令用于 ext2/ext3/ext4 文件系统
xfsdumpV:0, I:9854在 GNU/Linux 和 IRIX 上用 xfsdump(8) 和 xfsrestore(8) 命令来备份和恢复 XFS 文件系统
backupninjaV:4, I:5355轻量的可扩展的 meta-backup 系统
bacula-commonV:10, I:152158Bacula: 网络数据备份,恢复和核查-常见的支持文件
bacula-clientI:3183Bacula: 网络数据备份,恢复和核查-客户端元软件包
bacula-consoleV:1, I:5107Bacula: 网络数据备份,恢复和核查-文本终端
bacula-serverI:1183Bacula: 网络数据备份,恢复和核查-服务器端元软件包
amanda-commonV:0, I:210031Amanda: 马里兰大学开发的高级自动化网络磁盘归档器(库)
amanda-clientV:0, I:21089Amanda: 马里兰大学开发的高级自动化网络磁盘归档器(客户端)
amanda-serverV:0, I:01076Amanda: 马里兰大学开发的高级自动化网络磁盘归档器(服务器端)
backup-managerV:1, I:2572命令行备份工具
backup2lV:0, I:1114用于可挂载媒介 (基于磁盘的) 的低维护的备份/恢复工具
backuppcV:3, I:33182BackupPC 是用于备份 PC 机数据(基于磁盘)的高性能的企业级工具
duplicityV:7, I:151761(远程) 增量备份
flexbackupV:0, I:0243(远程) 增量备份
rdiff-backupV:7, I:15733(远程) 增量备份
resticV:1, I:320595(远程) 增量备份
rsnapshotV:5, I:11462(远程) 增量备份
slbackupV:0, I:0151(远程) 增量备份

备份工具有各自的专用的用途。

第 10.1.1 节 “存档和压缩工具”第 10.1.2 节 “复制和同步工具” 描述的基础工具能够通过自定义脚本来帮助系统备份。这些脚本的功能可以通过如下的工具来增强。

  • restic 软件包能够增量备份(远程)。

  • rdiff-backup 软件包能够增量备份(远程)。

  • dump 软件包用于高效增量的归档和恢复整个文件系统。

[提示]提示

参见 “/usr/share/doc/dump/“ 和 “Is dump really deprecated?” 来了解 dump 程序。

10.2.2. 一个系统备份的脚本例子

对于运行 unstable 套件的个人 Debian 桌面系统来说,只需要保护个人数据和关键数据。我不管怎样每年都会重新安装一次系统。因此没理由去备份整个系统或者安装全功能的备份实用程序。

我使用简单的脚本来制作用于备份的压缩文件并用 GUI 界面把它烧写到 CD/DVD 里。以下是关于这个的脚本例子。

  1. #!/bin/sh -e
  2. # Copyright (C) 2007-2008 Osamu Aoki <osamu@debian.org>, Public Domain
  3. BUUID=1000; USER=osamu # UID and name of a user who accesses backup files
  4. BUDIR="/var/backups"
  5. XDIR0=".+/Mail|.+/Desktop"
  6. XDIR1=".+/\.thumbnails|.+/\.?Trash|.+/\.?[cC]ache|.+/\.gvfs|.+/sessions"
  7. XDIR2=".+/CVS|.+/\.git|.+/\.svn|.+/Downloads|.+/Archive|.+/Checkout|.+/tmp"
  8. XSFX=".+\.iso|.+\.tgz|.+\.tar\.gz|.+\.tar\.bz2|.+\.cpio|.+\.tmp|.+\.swp|.+~"
  9. SIZE="+99M"
  10. DATE=$(date --utc +"%Y%m%d-%H%M")
  11. [ -d "$BUDIR" ] || mkdir -p "BUDIR"
  12. umask 077
  13. dpkg --get-selections \* > /var/lib/dpkg/dpkg-selections.list
  14. debconf-get-selections > /var/cache/debconf/debconf-selections
  15. {
  16. find /etc /usr/local /opt /var/lib/dpkg/dpkg-selections.list \
  17. /var/cache/debconf/debconf-selections -xdev -print0
  18. find /home/$USER /root -xdev -regextype posix-extended \
  19. -type d -regex "$XDIR0|$XDIR1" -prune -o -type f -regex "$XSFX" -prune -o \
  20. -type f -size "$SIZE" -prune -o -print0
  21. find /home/$USER/Mail/Inbox /home/$USER/Mail/Outbox -print0
  22. find /home/$USER/Desktop -xdev -regextype posix-extended \
  23. -type d -regex "$XDIR2" -prune -o -type f -regex "$XSFX" -prune -o \
  24. -type f -size "$SIZE" -prune -o -print0
  25. } | cpio -ov --null -O $BUDIR/BU$DATE.cpio
  26. chown $BUUID $BUDIR/BU$DATE.cpio
  27. touch $BUDIR/backup.stamp

这是一个用 root 权限执行的脚本例子。

我建议你按照如下所示的去更改和执行这个脚本。

把事情简单化!

[提示]提示

你能够用 “debconf-set-selections debconf-selections“ 命令恢复 debconf 配置数据,可以用 “dpkg —set-selection <dpkg-selections.list“ 命令恢复 dpkg 筛选数据。

10.2.3. 用于备份数据的复制脚本

对于目录树下面的数据集,”cp -a“ 命令可以实现常规备份。

对于类似 “/var/cache/apt/packages/“ 目录下面的大量不可覆盖的静态数据集,使用 “cp -al“ 命令来创建硬链接是一种替代常规备份的方式,这样可以高效的利用磁盘空间。

以下是一个用于数据备份的名为 bkup 的复制脚本。它把当前目录下的所有 (non-VCS) 文件复制到父目录下的指定目录中或者远程主机上。

  1. #!/bin/sh -e
  2. # Copyright (C) 2007-2008 Osamu Aoki <osamu@debian.org>, Public Domain
  3. fdot(){ find . -type d \( -iname ".?*" -o -iname "CVS" \) -prune -o -print0;}
  4. fall(){ find . -print0;}
  5. mkdircd(){ mkdir -p "$1";chmod 700 "$1";cd "$1">/dev/null;}
  6. FIND="fdot";OPT="-a";MODE="CPIOP";HOST="localhost";EXTP="$(hostname -f)"
  7. BKUP="$(basename $(pwd)).bkup";TIME="$(date +%Y%m%d-%H%M%S)";BU="$BKUP/$TIME"
  8. while getopts gcCsStrlLaAxe:h:T f; do case $f in
  9. g) MODE="GNUCP";; # cp (GNU)
  10. c) MODE="CPIOP";; # cpio -p
  11. C) MODE="CPIOI";; # cpio -i
  12. s) MODE="CPIOSSH";; # cpio/ssh
  13. t) MODE="TARSSH";; # tar/ssh
  14. r) MODE="RSYNCSSH";; # rsync/ssh
  15. l) OPT="-alv";; # hardlink (GNU cp)
  16. L) OPT="-av";; # copy (GNU cp)
  17. a) FIND="fall";; # find all
  18. A) FIND="fdot";; # find non CVS/ .???/
  19. x) set -x;; # trace
  20. e) EXTP="${OPTARG}";; # hostname -f
  21. h) HOST="${OPTARG}";; # user@remotehost.example.com
  22. T) MODE="TEST";; # test find mode
  23. \?) echo "use -x for trace."
  24. esac; done
  25. shift $(expr $OPTIND - 1)
  26. if [ $# -gt 0 ]; then
  27. for x in $@; do cp $OPT $x $x.$TIME; done
  28. elif [ $MODE = GNUCP ]; then
  29. mkdir -p "../$BU";chmod 700 "../$BU";cp $OPT . "../$BU/"
  30. elif [ $MODE = CPIOP ]; then
  31. mkdir -p "../$BU";chmod 700 "../$BU"
  32. $FIND|cpio --null --sparse -pvd ../$BU
  33. elif [ $MODE = CPIOI ]; then
  34. $FIND|cpio -ov --null | ( mkdircd "../$BU"&&cpio -i )
  35. elif [ $MODE = CPIOSSH ]; then
  36. $FIND|cpio -ov --null|ssh -C $HOST "( mkdircd \"$EXTP/$BU\"&&cpio -i )"
  37. elif [ $MODE = TARSSH ]; then
  38. (tar cvf - . )|ssh -C $HOST "( mkdircd \"$EXTP/$BU\"&& tar xvfp - )"
  39. elif [ $MODE = RSYNCSSH ]; then
  40. rsync -aHAXSv ./ "${HOST}:${EXTP}-${BKUP}-${TIME}"
  41. else
  42. echo "Any other idea to backup?"
  43. $FIND |xargs -0 -n 1 echo
  44. fi

如上只是一个范例。在你自己使用脚本之前,请阅读此脚本并且修改它。

[提示]提示

我把 bkup 保存在我的 “/usr/local/bin“ 目录。我假定当需要临时快照备份的时候,能够在工作目录不带任何参数运行 bkup 命令。

[提示]提示

如果是要制作源文件树或者配置文件树的快照历史的话,使用 git(7) (参见第 10.6.5 节 “记录配置历史的 Git”) 是更简便并且也是空间高效的。

10.3. 数据安全基础

数据安全基础设施是数据加密,讯息摘要和签名工具的结合。

表 10.6. 数据安全基础工具列表

软件包流行度大小命令说明
gnupgV:531, I:950787gpg(1)GNU 隐私卫士 - OpenPGP 加密和签名工具
gpgvV:880, I:999859gpgv(1)GNU 隐私卫士 - 签名验证工具
paperkeyV:1, I:1358paperkey(1)从 OpenPGP 私钥里面,仅仅导出私密信息
cryptsetupV:29, I:78402cryptsetup(8), …dm-crypto 块设备加密支持 LUKS 工具
ecryptfs-utilsV:3, I:5460ecryptfs(7), …ecryptfs 堆叠文件系统加密工具
coreutilsV:891, I:99917478md5sum(1)计算与校验 MD5 讯息摘要
coreutilsV:891, I:99917478sha1sum(1)计算与校验 SHA1 讯息摘要
opensslV:794, I:9931465openssl(1ssl)使用 “openssl dgst“ (OpenSSL)计算信息摘要

参见 第 9.8 节 “数据加密提示”dm-cryptoecryptfs,它们通过 Linux 内核模块实现了自动数据加密架构。

10.3.1. GnuPG 密钥管理

如下是 GNU 隐私卫士 基本的密钥管理命令。

表 10.7. GNU 隐私卫士密钥管理命令的列表

命令说明
gpg —gen-key生成一副新的密钥对
gpg —gen-revoke my_user_ID生成 my_user_ID 的一份吊销证书
gpg —edit-key user_ID交互式的编辑密钥,输入 “help” 来获得帮助信息
gpg -o file —export把所有的密钥输出到文件
gpg —import file从文件导入密钥
gpg —send-keys user_ID发送 user_ID 的公钥到公钥服务器
gpg —recv-keys user_ID从公钥服务器下载 user_ID 的公钥
gpg —list-keys user_ID列出 user_ID 的所有密钥
gpg —list-sigs user_ID列出 user_ID 的签字
gpg —check-sigs user_ID检查 user_ID 密钥签字
gpg —fingerprint user_ID检查 user_ID 的指纹
gpg —refresh-keys更新本地密钥

信任码含义.

表 10.8. 信任码含义列表

代码信任描述
-没有所有者信任签名/没有计算
e信任计算失败
q没有足够的信息用于计算
n从不信任这个键
m最低限度的信任
f完全信任
u最终信任

如下命令上传我的 “1DD8D791“ 公钥到主流的公钥服务器 “hkp://keys.gnupg.net“。

  1. $ gpg --keyserver hkp://keys.gnupg.net --send-keys 1DD8D791

默认良好的公钥服务器在 “~/.gnupg/gpg.conf“ (旧的位置在 “~/.gnupg/options“)文件中设置,此文件包含了以下信息。

  1. keyserver hkp://keys.gnupg.net

从钥匙服务器获取无名钥匙。

  1. $ gpg --list-sigs --with-colons | grep '^sig.*\[User ID not found\]' |\
  2. cut -d ':' -f 5| sort | uniq | xargs gpg --recv-keys

有一个错误在 OpenPGP 公钥服务器 (先前的版本 0.9.6),会将键中断为 2 个以上的子键。新的 gnupg (>1.2.1-2) 软件包能够处理这些中断的子键。参见 gpg(1) 下的 “--repair-pks-subkey-bug“ 选项.

10.3.2. 在文件上使用 GnuPG

这里有一些在文件上使用 GNU 隐私卫士 命令的例子。

表 10.9. 在文件上使用的 GNU 隐私卫士的命令列表

命令说明
gpg -a -s fileASCII 封装的签名文件 file.asc
gpg —armor —sign file同上
gpg —clearsign file生成明文签字信息
gpg —clearsign file|mail foo@example.org发送一份明文签字到 foo@example.org
gpg —clearsign —not-dash-escaped patchfile明文签名的补丁文件
gpg —verify file验证明文文件
gpg -o file.sig -b file生成一份分离的签字
gpg -o file.sig —detach-sig file同上
gpg —verify file.sig file使用 file.sig 验证文件
gpg -o crypt_file.gpg -r name -e file公钥加密,从文件里面获取名字,生成二进制的 crypt_file.gpg
gpg -o crypt_file.gpg —recipient name —encrypt file同上
gpg -o crypt_file.asc -a -r name -e file公钥加密,从文件中获取名字,生成 ASCII 封装的 crypt_file.asc
gpg -o crypt_file.gpg -c file将文件对称加密到 crypt_file.gpg
gpg -o crypt_file.gpg —symmetric file同上
gpg -o crypt_file.asc -a -c file对称加密,从文件到 ASCII 封装的 crypt_file.asc
gpg -o file -d crypt_file.gpg -r name解密
gpg -o file —decrypt crypt_file.gpg同上

10.3.3. 在 Mutt 中使用 GnuPG

增加下面内容到 “~/.muttrc“,在自动启动时,避免一个慢的 GnuPG,在索引菜单中按 “S“ 来允许它使用。

  1. macro index S ":toggle pgp_verify_sig\n"
  2. set pgp_verify_sig=no

10.3.4. 在 Vim 中使用 GnuPG

gnupg 插件可以让你对扩展名为 “.gpg“, “.asc“, 和 “.ppg“的文件可靠的运行 GnuPG.

  1. # aptitude install vim-scripts vim-addon-manager
  2. $ vim-addons install gnupg

10.3.5. MD5 校验和

md5sum(1) 提供了制作摘要文件的一个工具,它使用 rfc1321 里的方式制作摘要文件.

  1. $ md5sum foo bar >baz.md5
  2. $ cat baz.md5
  3. d3b07384d113edec49eaa6238ad5ff00 foo
  4. c157a79031e1c40f85931829bc5fc552 bar
  5. $ md5sum -c baz.md5
  6. foo: OK
  7. bar: OK
[注意]注意

MD5 校验和的 CPU 计算强度是比 GNU Privacy Guard (GnuPG) 加密签名要少的.在通常情况下,只有顶级的摘要文件才需要加密签名来确保数据完整性.

10.4. 源代码合并工具

这里有许多源代码合并工具。如下的是我感兴趣的工具。

表 10.10. 源代码合并工具列表

软件包流行度大小命令说明
diffutilsV:871, I:9911598diff(1)逐行比较两个文件
diffutilsV:871, I:9911598diff3(1)逐行比较和合并三个文件
vimV:106, I:3983231vimdiff(1)在 vim 中并排比较两个文件
patchV:99, I:725248patch(1)给原文件打补丁
dpatchV:0, I:11191dpatch(1)管理 Debian 软件包的系列补丁
diffstatV:16, I:15473diffstat(1)通过 diff 生成一个改变柱状图
patchutilsV:18, I:150232combinediff(1)从两个增量补丁创建一个积累补丁
patchutilsV:18, I:150232dehtmldiff(1)从一个 HTML 页面提取出一个 diff
patchutilsV:18, I:150232filterdiff(1)从一个 diff 文件里面提取或者排除 diff 文件
patchutilsV:18, I:150232fixcvsdiff(1)修复由 CVS patch(1) 错误创建的 diff 文件
patchutilsV:18, I:150232flipdiff(1)交换两个补丁的顺序
patchutilsV:18, I:150232grepdiff(1)显示哪些文件是由匹配正则表达式的补丁修改
patchutilsV:18, I:150232interdiff(1)显示在两个统一格式 diff 文件(基于同一个文件的两个不同 diff 文件)之间的差异
patchutilsV:18, I:150232lsdiff(1)显示哪些文件由补丁修改
patchutilsV:18, I:150232recountdiff(1)重新计算通用内容 diff 文件的数量和偏移
patchutilsV:18, I:150232rediff(1)修复手工编辑 diff 文件的数量和偏移
patchutilsV:18, I:150232splitdiff(1)隔离出增量补丁
patchutilsV:18, I:150232unwrapdiff(1)识别已经被分词的补丁
wiggleV:0, I:0174wiggle(1)应用被拒绝的补丁
quiltV:3, I:33788quilt(1)管理系列补丁
meldV:14, I:392972meld(1)比较和移植文件(GTK)
dirdiffV:0, I:2166dirdiff(1)显示目录树之间的不同并移植改变
docdiffV:0, I:0555docdiff(1)逐词逐字地比较两个文件
imediffV:0, I:0157imediff(1)全屏交互式两路/三路合并工具
makepatchV:0, I:0102makepatch(1)生成扩展补丁文件
makepatchV:0, I:0102applypatch(1)应用扩展补丁文件
wdiffV:9, I:72644wdiff(1)在文本文件中,显示单词的不同

10.4.1. 从源代码文件导出差异

下面的操作,导出两个源文件的不同,并根据文件的位置,创建通用 diff 文件”file.patch0“ 或 “file.patch1“.

  1. $ diff -u file.old file.new > file.patch0
  2. $ diff -u old/file new/file > file.patch1

10.4.2. 源代码文件移植更新

diff 文件(通常被叫作 patch 补丁文件),用于发送一个程序更新。通过下面的方式,接收到的部分,应用这个更新到其它文件。

  1. $ patch -p0 file < file.patch0
  2. $ patch -p1 file < file.patch1

10.4.3. 通过三方移植进行更新

如果一个源代码,你有三个版本,你可以通过下面的方式,使用 diff3(1) 高效执行三方移植。

  1. $ diff3 -m file.mine file.old file.yours > file

10.5. 版本控制系统

如下是 Debian 系统上可用的版本控制系统(VCS) 的摘要。

[注意]注意

如果是刚接触版本控制系统,你应该从 git 入门,git 人气日益高涨。

表 10.11. 版本控制系统工具列表

软件包流行度大小工具VCS 类型描述
csscV:0, I:22044CSSC本地Unix SCCS (过时)的克隆
rcsV:3, I:19562RCS本地“比 Unix SCCS 做的好”
cvsV:5, I:414609CVS远程以前的远程 VCS 标准
subversionV:20, I:1094858Subversion远程”比 CVS 做的好“,远程 VCS 的新标准
gitV:305, I:47835040Git分布式用 C 写的快速 DVCS (被 Linux 内核和其他项目使用)
mercurialV:8, I:481053Mercurial分布式mercurial 主要是用 Python 写的还有一部分是 C 写的
bzrV:2, I:1628Bazaar分布式tla 启发并且是用 Python 写的 DVCS (被 Ubuntu 使用)
darcsV:0, I:723159Darcs分布式有智能代数补丁的 DVCS(慢)
tlaV:0, I:21011GNU arch分布式主要由 Tom Lord 写的 DVCS (成为历史的)
monotoneV:0, I:05815Monotone分布式用 C++ 写的 DVCS
tkcvsV:0, I:11498CVS, …远程VCS (CVS,Subversion,RCS) 存储库树的图形界面显示
gitkV:6, I:421723Git分布式VCS (Git) 存储库树的图形界面显示

VCS 有时被认为是修订控制系统 (RCS), 或者是软件配置管理程序 (SCM)。

像 Git 这样的分布式 VCS 是现在正在使用的工具。参加那些已经存在的开源软件的开发活动,掌握 CVS 和 Subversion 仍然是有用的。

通过 Debian Salsa 服务,Debian 能够提供免费的 Git 服务。在 https://wiki.debian.org/Salsa 能找到它的说明文档。

[小心]小心

Debian 已经停止了其旧有的 alioth 服务,旧的 alioth 服务数据可以在 alioth-archive 站点上以 tar 压缩包的形式获取。

这里有一些关于创建共享访问 VCS 归档的基础知识。

10.5.1. VCS 命令的比较

这里有原生 VCS 命令的简单比较来提供大图概要。典型的命令序列需要选项和参数。

表 10.12. 本地 VCS 命令比较

GitCVSSubversion功能
git initcvs initsvn create创建(本地)存储库
-cvs login-登录远程存储库
git clonecvs cosvn co签出远程存储库到本地工作目录树
git pullcvs upsvn up通过合并远程存储库来更新工作目录树
git add .cvs addsvn add把工作目录树中的文件添加到 VCS
git rmcvs rmsvn rm从 VCS 中移除工作目录树中的文件
-cvs cisvn ci提交改变到远程存储库
git commit -a--提交改变到本地存储库
git push--通过本地存储库来更新远程存储库
git statuscvs statussvn status从 VCS 中显示工作目录树的状态
git diffcvs diffsvn diff比较<参考存储库>和<工作目录树>的差异
git repack -a -d; git prune--重新打包本地仓库到一个单独的包
gitktkcvstkcvsVCS 存储库树的图形界面显示
[小心]小心

从命令行通过 “git-xyz“ 直接调用 git 子命令的方式,从 2006 年早期开始就被取消。

[提示]提示

如果有一个可执行文件 git-foo 在路径环境变量 $PATH 里面,在命令行输入没有中划线的 “git foo“,则将调用 git-foo.这是 git 命令的一个特性.

[提示]提示

例如 tkcvs(1) 和 gitk(1) 这样的图形界面工具有助于追踪文件的修改历史。许多公共的归档提供的用于浏览它们的存储库的 web 界面同样是很有用的。

[提示]提示

Git 能够直接在不同的 VCS 仓库上工作,比如说 CVS 和 Subversion 提供的仓库, 通过 git-cvsgit-svn 软件包提供本地仓库的本地改变.参见 用于 CVS 用户的 git第 10.6.4 节 “用于 Subversion 仓库的 Git”.

[提示]提示

Git 中的有些命令在 CVS 和 Subversion 中并没有对应的命令:”fetch”,”rebase”,”cherry-pick”, …

10.6. Git

Git 可以用来做本地和远程源代码管理的任何事情。这意味着,你能够在本地记录源代码修改,而不是必须要和远程仓库有网络连接。

10.6.1. 配置 Git 客户端

你可以在 “~/.gitconfig“ 里面设置几个 Git 接下来需要使用的全局配置,比如说你的名字和电子邮件地址。

  1. $ git config --global user.name "姓名"
  2. $ git config --global user.email 电子邮件地址

如果你习惯使用 CVS 或 Subversion 命令,你也许希望设置如下几个命令别名。

  1. $ git config --global alias.ci "commit -a"
  2. $ git config --global alias.co checkout

你能够通过如下方式检查你的全局配置:

  1. $ git config --global --list

10.6.2. Git 参考

参见下面内容。

git-gui(1) 和 gitk(1) 命令使 Git 变得非常容易使用。

[警告]警告

不要使用带空格的标签字符串。即使一些工具,如 gitk(1) 允许你使用它,但会阻碍其它 git 命令。

10.6.3. Git 命令

即使你的上游使用不同的版本控制系统,使用 git(1) 作为本地活动的版本控制系统,仍然是一个好的主意,因为 git 可以让你在没有上游网络连接的情况下,管理你的本地源代码树拷贝。这里有一些 git(1) 使用的包和命令。

表 10.13. git 相关包和命令列表

软件包流行度大小命令说明
git-docI:1511762N/AGit 官方文档
gitmagicI:1721N/A“Git 魔术”,易于理解的 Git 手册
gitV:305, I:47835040git(7)Git 快速、可扩展、分布式的版本控制系统
gitkV:6, I:421723gitk(1)有历史功能的 Git 图形仓库浏览器
git-guiV:2, I:242317git-gui(1)Git 图形界面(无历史功能)
git-svnV:1, I:221144git-svnimport(1)从 Subversion 导出数据,导入到 Git
git-svnV:1, I:221144git-svn(1)在 Subversion 和 Git 之间提供双向操作
git-cvsV:0, I:101279git-cvsimport(1)从 CVS 导出数据,导入到 Git
git-cvsV:0, I:101279git-cvsexportcommit(1)从 Git 中检出一个 CVS 的提交
git-cvsV:0, I:101279git-cvsserver(1)Git 的 CVS 服务模拟器
git-emailV:0, I:11966git-send-email(1)从 Git 用电子邮件发送收集到的补丁
stgitV:0, I:0603stg(1)封装的 git (Python)
git-buildpackageV:2, I:124193git-buildpackage(1)用 Git 自动制作 Debian 包
guiltV:0, I:0146guilt(7)封装的 git (SH/AWK/SED/…)
[提示]提示

在 git(1) 下,你在本地分支下进行了许多提交,稍后你可以使用 “git rebase -i master“ 之类的命令来重新组织改变历史。这可以使你制作一个干净的改变历史。参见 git-rebase(1) 和 git-cherry-pick(1).

[提示]提示

当你想要回到一个干净的工作目录,并且不丢失工作目录当前的状态,你可以使用 “git stash“.参见 git-stash(1).

10.6.4. 用于 Subversion 仓库的 Git

你可以把一个在 “svn+ssh://svn.example.org/project/module/trunk“ 的 Subversion 仓库检出到一个本地的 Git 仓库,使用”./dest“目录,并把修改提交回 Subversion 仓库。例如:

  1. $ git svn clone -s -rHEAD svn+ssh://svn.example.org/project dest
  2. $ cd dest
  3. ... 进行修改
  4. $ git commit -a
  5. ... 继续在本地用 git 工作
  6. $ git svn dcommit
[提示]提示

使用 “-rHEAD“ 能够避免克隆从 Subversion 仓库来的整个历史内容。

10.6.5. 记录配置历史的 Git

你可以使用 Git) 工具来手工记录按时间先后顺序的配置历史。这里是一个例子,让你练习记录”/etc/apt/“ 内容。

  1. $ cd /etc/apt/
  2. $ sudo git init
  3. $ sudo chmod 700 .git
  4. $ sudo git add .
  5. $ sudo git commit -a

提交配置,描述此次提交。

对配置文件进行修改。

  1. $ cd /etc/apt/
  2. $ sudo git commit -a

提交配置,说明提交,继续你的工作。

  1. $ cd /etc/apt/
  2. $ sudo gitk --all

你有全部的配置历史。

[注意]注意

sudo(8) 是需要用于配置数据文件,任意文件权限的情况。 对于普通用户的配置数据,你需要省略 sudo

[注意]注意

在上面例子里的 “chmod 700 .git“ 命令,是用来保护文档数据不被未经授权的读访问。

[提示]提示

要更加完整的建立配置历史记录,请参阅 etckeeper 包: 第 9.2.10 节 “记录配置文件的变更”

10.7. CVS

CVS 是一个古老的版本控制系统,它的出现早于 Subversion 和 Git。

[小心]小心

下面例子里给出的 CVS 相关的链接许多已不存在。

参见下面内容。

  • cvs(1)

  • /usr/share/doc/cvs/html-cvsclient

  • /usr/share/doc/cvs/html-info

  • /usr/share/doc/cvsbook

  • info cvs

10.7.1. CVS 存储库的配置

如下的配置将只允许 “src“ 组的成员向 CVS 存储库提交修改 ,只允许 “staff“ 组的成员管理 CVS,这样可以减少出错的机会。

  1. # cd /var/lib; umask 002; mkdir cvs
  2. # export CVSROOT=/srv/cvs/project
  3. # cd $CVSROOT
  4. # chown root:src .
  5. # chmod 2775 .
  6. # cvs -d $CVSROOT init
  7. # cd CVSROOT
  8. # chown -R root:staff .
  9. # chmod 2775 .
  10. # touch val-tags
  11. # chmod 664 history val-tags
  12. # chown root:src history val-tags
[提示]提示

你可以改变 “$CVSROOT“ 目录为 “root:staff“ 并把它的权限设置为 “3775“,这样就可以限制创建新的项目。

10.7.2. 本地访问 CVS

默认的 CVS 存储库由 “$CVSROOT“ 指定。如下将建立用于本地访问的 “$CVSROOT“。

  1. $ export CVSROOT=/srv/cvs/project

10.7.3. 使用 pserver 远程访问 CVS

许多公共 CVS 服务器可以通过 pserver 服务用 “anonymous“ 账户远程只读访问。例如,Debian 网站的内容曾经使用名为 webwml project 的仓库经由 Debian alioth 服务的 CVS 服务进行维护。如下命令曾被用来建立用于远程访问该旧 CVS 仓库的 “$CVSROOT“。

  1. $ export CVSROOT=:pserver:anonymous@anonscm.debian.org:/cvs/webwml
  2. $ cvs login
[注意]注意

因为 pserver 容易被窃听攻击并且是不安全的,所以写访问通常是被服务器管理员禁用的。

10.7.4. 使用 ssh 远程访问 CVS

如下所示的命令曾被用来配置“$CVS_RSH”和“$CVSROOT”变量,以此实现使用 SSH 远程访问旧的 Debian webwml 项目所使用的 CVS 仓库。

  1. $ export CVS_RSH=ssh
  2. $ export CVSROOT=:ext:account@cvs.alioth.debian.org:/cvs/webwml

你也可以使用 SSH 的公钥认证,这能够去除远程密码提示。

10.7.5. 往 CVS 导入新的源

按如下所示创建 “~/path/to/module1“ 路径下的新的本地源目录树。

  1. $ mkdir -p ~/path/to/module1; cd ~/path/to/module1

把文件添加到 “~/path/to/module1“ 下的新的本地源目录树。

使用如下的参数把文件导入到 CVS。

  • 模块名: “module1

  • 提供商标签: “Main-branch“ (用于整个分支的标签)

  • 发布标签: “Release-initial“ (用于特定发布版本的标签)

  1. $ cd ~/path/to/module1
  2. $ cvs import -m "Start module1" module1 Main-branch Release-initial
  3. $ rm -Rf . # optional

10.7.6. CVS 存储库中的文件权限

CVS 不会覆盖当前的存储库文件,只是用另外的文件来替代它。因此,存储库目录的写权限是很重要的。存储库位于 “/srv/cvs/project“ 的 “module1“,对于其下的每一个新模块而言,如果需要的话运行如下所示的来确保这种情况。

  1. # cd /srv/cvs/project
  2. # chown -R root:src module1
  3. # chmod -R ug+rwX module1
  4. # chmod 2775 module1

10.7.7. CVS 工作流

这里有一个 CVS 典型工作流的例子。

按如下所示查看 “$CVSROOT“ 所指的 CVS 项目上所有可用的模块。

  1. $ cvs rls
  2. CVSROOT
  3. module1
  4. module2
  5. ...

按如下所示签出 “module1“ 到默认的目录 “./module1“。

  1. $ cd ~/path/to
  2. $ cvs co module1
  3. $ cd module1

按需修改里面的内容。

通过如下所示的命令来检查改变,其作用相当于使用 “diff -u [repository] [local]“。

  1. $ cvs diff -u

你发现自己改坏了 “file_to_undo“ 文件,而其他的文件都是好的。

按如下所示用 CVS 中的原始副本覆盖 “file_to_undo“ 文件。

  1. $ cvs up -C file_to_undo

按如下所示把更新了的本地源目录树保存到 CVS。

  1. $ cvs ci -m "Describe change"

按如下创建并添加 “file_to_add“ 文件到 CVS。

  1. $ vi file_to_add
  2. $ cvs add file_to_add
  3. $ cvs ci -m "Added file_to_add"

按如下所示合并 CVS 中的最新版本。

  1. $ cvs up -d

当心以 “C filename“ 开头的行,这意味着冲突的改变。

查看 “.#filename.version“ 中未经修改的代码。

查找文件中的 “<<<<<<<“ 和 “>>>>>>>“ 来获得冲突的改变的信息。

按需更改文件来解决冲突。

按如下所示添加一个发布标签 “Release-1“。

  1. $ cvs ci -m "last commit for Release-1"
  2. $ cvs tag Release-1

继续编辑文件。

按如下所示移除发布分支 “Release-1“。

  1. $ cvs tag -d Release-1

按如下所示把改变签入到 CVS。

  1. $ cvs ci -m "real last commit for Release-1"

按如下所示给已经更新了的 CVS 主干中的 HEAD 重新添加 “Release-1“ 发布标签。

  1. $ cvs tag Release-1

按如下所示从 “Release-initial“ 标签指向的初始版本中创建一个带有粘性标签的 “Release-initial-bugfixes“ 分支,并把它签出到 “~/path/to/old“ 目录。

  1. $ cvs rtag -b -r Release-initial Release-initial-bugfixes module1
  2. $ cd ~/path/to
  3. $ cvs co -r Release-initial-bugfixes -d old module1
  4. $ cd old
[提示]提示

使用 “-D 2005-12-20“ (ISO 8601 日期格式) 而不是 “-r Release-initial“ 来指定某个特定日期作为分支点。

在基于原始版本的有 “Release-initial-bugfixes“ 粘性标签的本地源目录树上工作。

独自在 “Release-initial-bugfixes“ 分支上工作…直到有其他人加入到此分支。

当要创建新的目录时,按如下所示同步其他人在此分支上对文件所做的修改。

  1. $ cvs up -d

按需更改文件来解决冲突。

按如下所示把改变签入到 CVS。

  1. $ cvs ci -m "checked into this branch"

按如下所示更新本地目录树为主干的最新版本,同时移除粘性标签 (“-A“) 并且不使用关键字扩展 (“-kk“)。

  1. $ cvs up -d -kk -A

按如下所示通过合并 “Release-initial-bugfixes“ 分支并且不使用关键字扩展的方式来更新本地目录树 (内容为主干中的最新版本)。

  1. $ cvs up -d -kk -j Release-initial-bugfixes

用编辑器来解决冲突。

按如下所示把改变签入到 CVS。

  1. $ cvs ci -m "merged Release-initial-bugfixes"

按如下所示创建归档。

  1. $ cd ..
  2. $ mv old old-module1-bugfixes
  3. $ tar -cvzf old-module1-bugfixes.tar.gz old-module1-bugfixes
  4. $ rm -rf old-module1-bugfixes
[提示]提示

cvs up“ 命令能够使用 “-d“ 选项来创建新目录并且可以使用 “-P“ 选项来删除空目录。

[提示]提示

你可以通过形如 “cvs co module1/subdir“ 这样的列出其名的方式,来签出 “module1“ 的一个子目录。

表 10.14. 值得注意的 CVS 命令选项 (用作 cvs(1) 的第一个选项)

选项说明
-n测试,没有影响
-t显示 cvs 活动步骤的信息

10.7.8. CVS 中最新的文件

按如下所示使用 “tomorrow“ 选项,就能得到 CVS 中的最新文件。

  1. $ cvs ex -D tomorrow module_name

10.7.9. CVS 的管理

按如下所示往 CVS 项目 (本地服务器) 里添加 “mx“ 模块别名。

  1. $ export CVSROOT=/srv/cvs/project
  2. $ cvs co CVSROOT/modules
  3. $ cd CVSROOT
  4. $ echo "mx -a module1" >>modules
  5. $ cvs ci -m "Now mx is an alias for module1"
  6. $ cvs release -d .

按如下所示,你可以从 CVS 中签出 “module1“ (别名为: “mx“) 到 “new“ 目录。

  1. $ cvs co -d new mx
  2. $ cd new
[注意]注意

为了执行上述步骤,你应当有合适的文件权限。

10.7.10. 用于 CVS 签出时的可执行位

当你从 CVS 中签出文件时,它们的可执行权限是保留的。

当你发现在检出的形如 “filename“ 这样的文件中,可执行权限有问题时,按如下所示在相应的 CVS 存储库中改变文件的权限来解决这个问题。

  1. # chmod ugo-x filename

10.8. Subversion

Subversion 是在 Git 之前出现的旧的版本控制系统,但它出现在 CVS 之后。它缺少 CVS 和 Git 中的标签和分支功能。

你需要安装 subversionlibapache2-mod-svnsubversion-tools 软件包来搭建 Subversion 服务器。

10.8.1. Subversion 存储库的配置

subversion 软件包通常不会自动建立存储库,所以你必须手动搭建它。存储库可能的位置是在 “/srv/svn/project“。

按如下所示建立目录。

  1. # mkdir -p /srv/svn/project

按如下所示建立存储库数据库。

  1. # svnadmin create /srv/svn/project

10.8.2. 通过 Apache2 服务器访问 Subversion

如果只是用 Apache2 服务器访问 Subversion 存储库,你只需按如下所示的使存储库只是对于 WWW 服务器是可写的。

  1. # chown -R www-data:www-data /srv/svn/project

在 “/etc/apache2/mods-available/dav_svn.conf“ 中添加 (或取消注释) 如下所示的来允许通过用户认证访问存储库。

  1. <Location /project>
  2. DAV svn
  3. SVNPath /srv/svn/project
  4. AuthType Basic
  5. AuthName "Subversion repository"
  6. AuthUserFile /etc/subversion/passwd
  7. <LimitExcept GET PROPFIND OPTIONS REPORT>
  8. Require valid-user
  9. </LimitExcept>
  10. </Location>

用如下所示的命令创建用户认证文件。

  1. # htpasswd2 -c /etc/subversion/passwd some-username

重启 Apache2。

通过 “http://localhost/project“ 和 “http://example.com/project“ URL 来访问 svn(1) 中的 Subversion 存储库(假设你的 web 服务器的 URL 为 “http://example.com/“)。

10.8.3. 按组本地访问 Subversion

如下所示将建立用户组,例如 project,可以本地访问的 Subversion 存储库。

  1. # chmod 2775 /srv/svn/project
  2. # chown -R root:src /srv/svn/project
  3. # chmod -R ug+rwX /srv/svn/project

属于 project 组的本地用户可以访问在 “file:///localhost/srv/svn/project“ 或 file:///srv/svn/project“ 下 svn(1) 中的新 Subversion 存储库。你必须在 “umask 002“ 下运行诸如 svn, svnserve, svnlooksvnadmin 命令,来确保用户组可以访问。

10.8.4. 通过 SSH 远程访问 Subversion

用户组可以访问的 Subversion 存储库的 URL 为 “example.com:/srv/svn/project“。至于 SSH 访问,你能够在 svn(1) 中的 “svn+ssh://example.com:/srv/svn/project“ URL 访问它。

10.8.5. Subversion 目录结构

对于 Subversion 来说,许多项目使用类似如下的目录树来弥补它的分支和标签的不足。

  1. ----- module1
  2. | |-- branches
  3. | |-- tags
  4. | | |-- release-1.0
  5. | | `-- release-2.0
  6. | |
  7. | `-- trunk
  8. | |-- file1
  9. | |-- file2
  10. | `-- file3
  11. |
  12. `-- module2
[提示]提示

你必须使用 “svn copy …“ 命令来标记分支和标签。这确保 Subversion 正确的记录文件的修改历史同时节省存储空间。

10.8.6. 往 Subversion 里导入一个新的源

按如下所示创建 “~/path/to/module1“ 路径下的新的本地源目录树。

  1. $ mkdir -p ~/path/to/module1; cd ~/path/to/module1

把文件添加到 “~/path/to/module1“ 下的新的本地源目录树。

把它导入到 Subversion 的时候带有以下的参数。

  • 模块名: “module1

  • Subversion 位置 URL: “file:///srv/svn/project

  • Subversion 目录: “module1/trunk

  • Subversion 标签: “module1/tags/Release-initial

  1. $ cd ~/path/to/module1
  2. $ svn import file:///srv/svn/project/module1/trunk -m "Start module1"
  3. $ svn cp file:///srv/svn/project/module1/trunk file:///srv/svn/project/module1/tags/Release-initial

或者,如下所示。

  1. $ svn import ~/path/to/module1 file:///srv/svn/project/module1/trunk -m "Start module1"
  2. $ svn cp file:///srv/svn/project/module1/trunk file:///srv/svn/project/module1/tags/Release-initial
[提示]提示

你能够用像 “http://…“ 和 “svn+ssh://…“ 这样格式的 URL 来替代 “file:///…“ URL。

10.8.7. Subversion 工作流

这里给出使用 Subversion 及其原生客户端的典型工作流示例。

[提示]提示

git-svn 软件包提供的客户端命令,可以作为使用 git 命令的 Subversion 工作流的一个另外选择。参见 第 10.6.4 节 “用于 Subversion 仓库的 Git”.

查看如下所示的 URL “file:///srv/svn/project“ 指向的 Subversion 项目上所有可用的模块。

  1. $ svn list file:///srv/svn/project
  2. module1
  3. module2
  4. ...

按如下所示的检出 “module1/trunk“ 到 “module1“ 目录。

  1. $ cd ~/path/to
  2. $ svn co file:///srv/svn/project/module1/trunk module1
  3. $ cd module1

按需修改里面的内容。

通过如下所示的命令来检查改变,其作用相当于使用 “diff -u [repository] [local]“。

  1. $ svn diff

你发现自己改坏了 “file_to_undo“ 文件,而其他的文件都是好的。

按如下所示的用 Subversion 中的干净副本来覆盖 “file_to_undo“ 文件。

  1. $ svn revert file_to_undo

按如下所示的把已经更新了的本地源目录树保存到 Subversion。

  1. $ svn ci -m "Describe change"

按如下所示的创建 “file_to_add“ 文件并把它添加到 Subversion。

  1. $ vi file_to_add
  2. $ svn add file_to_add
  3. $ svn ci -m "Added file_to_add"

按如下所示更新工作拷贝到 Subversion 中的最新版本。

  1. $ svn up

当心以 “C filename“ 开头的行,这意味着冲突的改变。

查看文件中未经修改的代码,例如 “filename.r6“, “filename.r9“ 和 “filename.mine“ 文件。

查找文件中的 “<<<<<<<“ 和 “>>>>>>>“ 来获得冲突的改变的信息。

按需更改文件来解决冲突。

按如下所示添加一个发布标签 “Release-1“。

  1. $ svn ci -m "last commit for Release-1"
  2. $ svn cp file:///srv/svn/project/module1/trunk file:///srv/svn/project/module1/tags/Release-1

继续编辑文件。

按如下所示移除发布分支 “Release-1“。

  1. $ svn rm file:///srv/svn/project/module1/tags/Release-1

按如下所示把改变签入到 Subversion。

  1. $ svn ci -m "real last commit for Release-1"

按如下所示在最新的 Subversion 主干的基础上重新添加发布分支 “Release-1“。

  1. $ svn cp file:///srv/svn/project/module1/trunk file:///srv/svn/project/module1/tags/Release-1

按如下所示在 “module1/tags/Release-initial“ 路径指定的最初版本的基础上再创建一个路径为 “module1/branches/Release-initial-bugfixes“ 的分支,并把它签出到 “~/path/to/old“ 目录。

  1. $ svn cp file:///srv/svn/project/module1/tags/Release-initial file:///srv/svn/project/module1/branches/Release-initial-bugfixes
  2. $ cd ~/path/to
  3. $ svn co file:///srv/svn/project/module1/branches/Release-initial-bugfixes old
  4. $ cd old
[提示]提示

使用 “module1/trunk@{2005-12-20}“ (ISO 8601 日期格式) 而不是 “module1/tags/Release-initial“ 来指定分支创建时候的日期。

在基于原始版本的 “Release-initial-bugfixes“ 分支的本地源目录树上工作。

独自在 “Release-initial-bugfixes“ 分支上工作…直到有其他人加入到此分支。

按如下所示同步其他人在此分支上改动的文件。

  1. $ svn up

按需更改文件来解决冲突。

按如下所示把改变签入到 Subversion。

  1. $ svn ci -m "checked into this branch"

按如下所示更新本地目录树为主干的最新版本。

  1. $ svn switch file:///srv/svn/project/module1/trunk

按如下所示通过合并 “Release-initial-bugfixes“ 分支的方式来更新本地目录树 (内容为主干的最新版本)。

  1. $ svn merge file:///srv/svn/project/module1/branches/Release-initial-bugfixes

用编辑器来解决冲突。

按如下所示把改变签入到 Subversion。

  1. $ svn ci -m "merged Release-initial-bugfixes"

按如下所示创建归档。

  1. $ cd ..
  2. $ mv old old-module1-bugfixes
  3. $ tar -cvzf old-module1-bugfixes.tar.gz old-module1-bugfixes
  4. $ rm -rf old-module1-bugfixes
[提示]提示

你能够用像 “http://…“ 和 “svn+ssh://…“ 这样格式的 URL 来替代 “file:///…“ URL。

[提示]提示

通过 “svn co file:///srv/svn/project/module1/trunk/subdir module1/subdir“ 等命令,你可以只签出 “module1“ 的一个子目录。

表 10.15. 值得注意的 Subversion 命令选项 (使用时作为 svn(1) 的第一个参数)

选项说明
—dry-run测试,没有影响
-v显示 svn 活动的详细信息