Linux 命令:cpio
提到 Linux 下的归档命令,绝大多数人第一个想到的就是 tar。打包用 tar,解包用 tar,看压缩包内容还是用 tar,以至于很多运维干了好几年都没碰过 cpio。说实话,我在日常工作里用的最多的也是 tar,真正让我静下心来研究 cpio,是因为一次意外:某个客户环境里的备份脚本是十年前的老前辈写的,用的正是 cpio。当时脚本出了问题,我对着报错一脸懵,从那以后我才意识到,cpio 并没有退出历史舞台,而是像老黄牛一样,在一个又一个不常被注意的角落里踏实干活。
cpio 的全称是 copy in and out,它比 tar 更古老,诞生于 Unix 早期,当年的定位就是"磁带备份工具"。它的设计哲学和 tar 完全不同,却恰恰因为这个差异,让它在某些场景下比 tar 好用得多。比如你手头有一个 RPM 包,临时想看里面的某个可执行文件长什么样,tar 是打不开的,但 rpm2cpio 配合 cpio 一条命令就能搞定。再比如嵌入式开发里经常需要手工制作 initramfs,官方推荐的制作方式也离不开 cpio。还有那些线上老服务器里不知道谁留下的备份脚本,动不动就能看到 find ... | cpio -o 的写法。可以说,cpio 是一个你平时用不上、但关键时刻绕不开的命令。
这篇文章我不会只给它列个参数表就完事,而是会按照我自己的理解和实验结论,把 cpio 的三种运行模式、核心参数、典型场景和实际坑点全部拆开讲透。无论你是刚接触 Linux 的新手,还是天天和服务器打交道的运维老手,这篇文章都能让你在遇到 cpio 时不再心虚。特别是在面试的时候,当面试官突然问一句"除了 tar,你还用过哪些归档命令",你能把 cpio 的原理和用途讲得明明白白,这就是一个很加分的差异化回答。
1. cpio 的核心思想:三种运行模式决定了它的一切
1.1 先搞懂 cpio 为什么和 tar 长得不一样
cpio 最反直觉的地方在于:它的"打包"命令不是 cpio 参数加文件名那么直接,而是必须通过标准输入把文件列表喂给它。
tar 的工作方式是"我给你一堆文件或目录,你把它们都装进一个包"。cpio 的工作方式是"我只管处理标准输入传来的文件路径列表,每读到一个路径,就把这个文件的内容写入归档"。这看起来只是形式上的差异,实际上代表了两种完全不同的设计思路。
tar 是面向"文件树"的,你可以直接 tar czf backup.tar.gz /etc,它自己会递归遍历整个目录。cpio 是面向"文件流"的,它本身不做遍历这件事,你让它打包哪些文件,它才打包哪些文件。想要递归?那你要么自己用 find 列出所有路径,要么在早期版本中手动加 -d 等参数配合。这种设计让 cpio 有一种天然的优势:它和 find 的配合天衣无缝。
举个例子,你只想打包 /etc 目录下最近三天修改过的、名字以 .conf 结尾的文件。tar 实现这个需求会比较别扭,你可能得先 find 生成列表再传给 tar 的 -T 参数。而 cpio 直接就是为这种管道模式设计的:
bash复制find /etc -name "*.conf" -mtime -3 | cpio -o > conf_backup.cpio
find 输出的每一行路径,cpio 原封不动地读取,然后生成归档。整个过程中没有任何多余的动作,干净利落。这也是为什么很多老运维特别喜欢 cpio,因为它在"精确选择文件"这件事上做得比 tar 好太多。
cpio 的三种运行模式是理解这个命令的主干:
- 第一种是 copy-out 模式,用 -o 参数,作用是把文件打包进归档;
- 第二种是 copy-in 模式,用 -i 参数,作用是从归档中提取文件;
- 第三种是 copy-pass 模式,用 -p 参数,作用是在两个目录之间直接复制文件树,不走归档这个中间环节。
后面所有参数都是在为这三种模式服务。你把这三个模式记在心里,再去看参数就不会一团乱麻了。
1.2 三种模式的具体场景
copy-out 模式就是"往外拷贝",这里的"外"指的是标准输出或者指定的归档文件。最典型的用法就是前面说的 find | cpio -o。举个例子,你要把 /var/log 目录下所有日志文件打包:
bash复制find /var/log -type f | cpio -o > logs.cpio
cpio 会按 find 输出的顺序逐个读取文件并写入归档。如果你希望输出的是带压缩的归档,直接用管道传给 gzip 就行:
bash复制find /var/log -type f | cpio -o | gzip > logs.cpio.gz
这里你就体会到了 cpio 的"流式"特质:它不关心你后面接的是普通文件、压缩程序还是远程传输工具,它只是把归档写到标准输出,剩下的事情完全交给管道链上的其他程序。这种单一职责的设计,在写复杂脚本的时候其实非常舒服。
copy-in 模式是解包方向。一条典型的命令是:
bash复制cpio -id < logs.cpio
注意,我加了 -d 参数,这代表"自动创建必要的目录"。cpio 解包的时候,不会像 tar 那样默认把所有文件的原始路径都还原出来,如果你不加 -d,它只会在当前目录下放下单个文件,遇到需要多级目录的情况就直接报错。这是新手最容易踩的第一个坑。
copy-pass 模式相当于"边读边写"的快速复制。它不生成中间归档文件,直接把源目录的文件复制到目标位置:
bash复制find /data/source -depth | cpio -pdm /data/target
这种方式常用于需要精确控制复制文件列表,但又不想经过中间归档文件的场景。它保留了文件权限和时间戳,和 rsync 在功能上有一些重合,但在不含增量同步需求的老脚本中很常见。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. cpio 的核心参数详解:每个参数背后都有实际场景
2.1 常用参数速查表
cpio 的参数不算多,但是每个参数都有它存在的理由。我整理了一下日常用得最频繁的参数,你可以直接收藏这一节当速查表。
| 参数 | 所属模式 | 含义 | 典型使用场景 |
|---|---|---|---|
| -o | copy-out | 创建归档,从标准输入读取文件列表 | 配合 find 打包指定文件 |
| -i | copy-in | 从归档中提取文件 | 解包整个归档 |
| -p | copy-pass | 复制模式,从一个目录复制到另一个目录 | 精确复制文件树 |
| -d | 所有模式 | 自动创建必要的目录 | 解包目录结构时必须加 |
| -v | 所有模式 | verbose,显示处理的文件名 | 调试脚本,观察过程 |
| -t | copy-in | 列出归档内容而不实际提取 | 快速查看包里有什么 |
| -m | copy-in | 保留文件的修改时间 | 还原文件时间戳 |
| -u | copy-in | 无条件覆盖已有文件 | 强制覆盖解包 |
| -F | 所有模式 | 指定归档文件名 | 使用归档文件而非标准输入输出 |
| -H | 所有模式 | 指定归档格式 | 制作 initramfs 时设 newc |
| -B | copy-out | 设置块大小为 5120 字节 | 老式磁带设备优化 |
| --no-absolute-filenames | copy-in | 解包时去掉绝对路径的前导斜杠 | 防止解包覆盖绝对路径文件 |
我来重点解释几个容易被忽略但极其重要的参数。
2.2 -d 参数:解包不离手的安全带
我第一次用 cpio 解包的时候,没有加 -d,结果命令跑完了一看,目录根本没有按预想的结构出现。原因很简单:cpio 默认不会自动创建目录。tar 在这方面做得比较"亲民",你不管解一个多深的目录树,它都会自动把中间目录建好。而 cpio 的思路是"我按你说的做,你没说建目录我就不建"。
这个设计有好有坏。好处是你可以精确控制解包行为,比如你只需要提取归档里的单个文件,不想让解包动作在当前目录下创建一长串路径,那不加 -d 反而是个优势。坏处就是初学者不习惯,容易踩坑。我的建议是:除非你有特殊需求,否则解包命令固定写成 cpio -id,把 -d 焊死在肌肉记忆里。
需要注意 -d 在 copy-pass 模式下的行为。用 -p 配合 -d 复制时,cpio 会在目标目录下建立完整的源目录结构。这样做通常没问题,但有一种情况会出事:你的源列表里有绝对路径。比如:
bash复制find /etc -type f | cpio -pdm /opt/backup
这样执行后,/opt/backup 下面会出现 etc/ 目录,里面放着 /etc 的所有文件。cpio 在 copy-pass 模式下会把绝对路径前面的斜杠去掉,所以不会出现 /opt/backup/etc/ 这种被误解的路径。但如果你的 find 是带相对路径执行的,结果就会不一样,这点我在后面常见问题里会展开。
2.3 -m 和 -u:解包时容易被忽略的时间戳问题
用 cpio 解包老归档时,你会发现一个问题:文件内容都对了,但文件的时间戳全部变成了当前时间。这是因为 cpio 默认并不会保留文件的原始修改时间。tar 会默认保留,cpio 不会。所以当你需要严格保留文件时间戳时,必须加上 -m 参数:
bash复制cpio -idm < backup.cpio
这个参数在很多真实场景中非常关键。比如你从生产环境的备份归档中恢复文件,如果不加 -m,恢复出来的所有文件时间都是"现在",这会直接破坏后续依赖时间戳做增量备份的流程。我就遇到过因为这种小疏忽,导致备份系统重复备份了大量文件的情况,运维同学一度以为是备份脚本被写坏了,最后追查下来才发现是恢复时没保留时间戳。
-u 参数解决的是覆盖问题。cpio 默认提取文件时,如果当前目录下已经存在同名文件,它会拒绝覆盖并给出提示。如果你确定要覆盖,就需要加 -u。这个参数的思路和 tar 不一样,tar 默认就是"解出来覆盖同名的",而 cpio 默认是"我不动已存在的文件"。在高可用环境的配置恢复场景里,这个默认行为反而能起到保护作用,防止误操作把新配置文件给覆盖了。
2.4 归档格式:-H newc 和 odc 到底怎么选
cpio 在生成归档时有一个 -H 参数,用于指定归档格式。常见的有:
- bin 格式:旧的二进制格式,不推荐在现代系统中使用;
- odc 格式:POSIX 可移植字符格式,字段由 ASCII 码组成,兼容性极好;
- newc 格式:也被称为 SVR4 格式,是嵌入式 Linux 中 initramfs 的标准格式。
我最早接触 cpio 的时候,根本没管过格式这件事,反正生成的归档自己用 cpio 也能解开。直到有次我需要用其他工具读取 cpio 归档,才发现格式不兼容的尴尬。这里给出一个简易的经验法则:
如果你只是在 Linux 之间互相传递归档,用默认的 bin 或者 odc 都行,但更建议显式指定 newc,因为它的兼容面更广。如果你要制作 initramfs,那就必须用 newc 格式,因为 Linux 内核只认这种格式的 cpio 归档。如果你做的是跨平台的可移植性备份,odc 是最保险的选择,因为它是纯文本字段,任何平台解析起来都不会有字节序问题。
下面这条命令可以查看一个 cpio 归档的格式:
bash复制cpio -it < archive.cpio | head
如果文件头信息不对或者格式不对,cpio 会直接报错 "Not a cpio file" 或者 "Unrecognized file format"。这类报错通常不是文件损坏,而是你选用了错误的 -H 参数去读取。
3. 实操场景一:用 rpm2cpio 从 RPM 包里提取文件
3.1 为什么要从 RPM 包里提取文件
很多运维在排查问题时会遇到这样一种情况:某台服务器上的某个命令坏了,比如 /usr/bin/curl 被误删或者被覆盖成不兼容的版本。你手里没有源码包,也没有现成的编译环境,最快的修复方式就是从对应版本的 RPM 包里把原始 curl 文件提取出来,然后放回原位。
RPM 是 Red Hat 系发行版的软件包格式,本质上它是一个带有元数据的归档。但它不能直接用 tar 或 unzip 解开,因为它的内部结构是 cpio 归档。这时候 rpm2cpio 这个工具就派上用场了。rpm2cpio 的作用很简单:把 RPM 包转换成原始的 cpio 数据流。配合 cpio 的 copy-in 模式,你就能像解压普通归档一样浏览和提取 RPM 包里的任何文件。
这个技巧在你没有安装 yumdownloader、rpmrebuild 等工具的时候尤其救急。而且哪怕你有这些工具,在离线环境或者内网环境中,rpm2cpio 也是唯一不需要额外依赖就能完成工作的方法。
3.2 操作流程详解
步骤一,先把 RPM 包搞定。如果你在能联网的机器上,可以用 yumdownloader 下载,或者直接在软件包缓存目录里找。在 CentOS/RHEL 上,RPM 缓存一般位于 /var/cache/yum/ 或 /var/cache/dnf/ 下。如果你是从官方镜像站下载的 RPM,直接跳到下一步。
步骤二,用 rpm2cpio 把 RPM 转成 cpio 归档流,然后用 cpio 提取。一条命令搞定:
bash复制rpm2cpio curl-7.61.1-14.el8.x86_64.rpm | cpio -idmv
这条命令会在当前目录下生成类似 usr/bin/curl 的路径结构。接下来你只需要把解出来的 curl 复制回 /usr/bin/ 并加上执行权限即可:
bash复制cp usr/bin/curl /usr/bin/curl
chmod +x /usr/bin/curl
步骤三,如果你只是想查看 RPM 包里有哪些文件,不需要实际解包,可以用 -t 参数:
bash复制rpm2cpio curl-7.61.1-14.el8.x86_64.rpm | cpio -it
这会列出 RPM 包内的完整文件清单,比 rpm -qpl 更底层,适合你在对 RPM 内部结构有疑惑的时候使用。
3.3 实际操作中的经验
我提一个具体的坑。rpm2cpio 解出来的文件路径前面没有根斜杠,也就是说你看到的是 usr/bin/curl 而不是 /usr/bin/curl。很多人在当前目录下解压后,发现得到的是一堆相对路径目录,一时间反应不过来。这不是错误,是 cpio 在 copy-in 模式下的默认行为:去掉绝对路径的前导斜杠,防止解包时直接覆盖系统的绝对路径文件。这其实是一个安全设计。如果你真的需要保留绝对路径(比如备份恢复场景),可以加上 --absolute-filenames 参数,但要非常谨慎,因为这意味着如果归档内部写了 /etc/passwd,解包会直接覆盖系统上的 /etc/passwd。
另外,rpm2cpio 不是单独的软件包,它通常随 rpm 工具集一起安装。如果你在极简容器环境里发现没有 rpm2cpio,可以用 rpm 自带的宏来替代:
bash复制rpm --showrc | grep _rpmconfigdir
一般来说这个宏指向 /usr/lib/rpm,rpm2cpio 就在这个目录下。如果实在找不到,也可以试试 dnf install rpm2cpio,很多新发行版已经把它独立出来了。
4. 实操场景二:手工制作 initramfs 镜像
4.1 为什么 initramfs 和 cpio 绑定在一起
嵌入式开发中经常要做的一件事是手工定制 initramfs,也就是在系统启动早期加载的内存根文件系统。它和传统 initrd 的一个显著区别是:initrd 是一个块设备镜像,而 initramfs 本质上就是一个 cpio 归档。Linux 内核在启动时会把 initramfs 解压到内存中作为临时根文件系统,而这个"解压"动作认识的就是 cpio newc 格式。这就是为什么你要手工制作 initramfs,最终总绕不开 cpio 命令。
当你使用 dracut、mkinitramfs 这些工具时,它们内部默认用的就是 cpio 来打包。如果你选择的输出格式是 "cpio",制作流程就是先把需要的文件、工具、库放到一个临时目录里,然后用 cd 进入目录,再通过管道将当前目录的所有文件打包成 cpio 归档。这个顺序特别重要。
4.2 手工打包 initramfs 的完整步骤
假设我准备好了一个临时目录 initramfs_root,里面放好了 busybox、必要的设备节点和配置文件。接下来制作镜像:
bash复制cd initramfs_root
find . | cpio -o -H newc | gzip > ../initramfs.img
看到没有,关键点有三个。第一,cd 到目录内再执行 find .,这样打包出的归档内部路径都是相对路径,以 . 开头,不会包含外层目录信息。第二,指定 -H newc,因为内核只认识这个格式。第三,用 gzip 压缩,内核也支持解压缩这种 cpio.gz 格式。
如果你不想压缩,直接保存为无后缀的 initramfs 或者 .cpio 也可以。但一般生产环境中加上 gzip 能显著缩小镜像体积,启动时内核会自动解压,不会影响性能。
反过来,假设你拿到一个 initramfs.img,需要解开它做修改,操作也很直接:
bash复制mkdir /tmp/initramfs_extract
cd /tmp/initramfs_extract
zcat ../initramfs.img | cpio -id
注意这里 cpio 不需要 -H 参数,它会自动检测归档格式。个别情况下,如果 initramfs 是未压缩的,直接 cpio -id < ../initramfs.img 就行。
4.3 制作中的几个隐性问题
制作 initramfs 时,文件的属主和权限在内核加载阶段并不会被校验,因为整个文件系统都是在内存中的。但如果你把 initramfs 用在某些安全启动环境里,配置文件的权限仍然可能影响启动流程。作为经验,我一般会在打包前先统一用 chown root:root 处理一遍目录树,避免文件属主混乱。
另一个问题是设备节点。在 initramfs 中手工创建设备节点时需要 root 权限。如果你是在普通用户下用 cpio 打包,遇到设备文件 cpio 会拒绝处理或者处理结果不符合预期。有些嵌入式环境的做法是省略设备节点,让内核在启动时通过 devtmpfs 自动生成。这也是更现代的做法。
5. 实操场景三:精确控制的备份与恢复
5.1 用 find + cpio 做指定条件的备份
运维场景中,最常见的一种 cpio 用法是"精确备份指定文件"。tar 虽然可以做到,但在条件复杂的场景下,你需要引入较绕的参数组合。cpio 和 find 的组合拳则可以直截了当地解决问题。
举一个真实的例子:某天我接到一个任务,要备份所有项目目录下超过 100MB、且不是临时文件的日志文件,不改变它们原来的相对路径。这个需求用 tar 处理很繁琐,用 cpio 却很顺手:
bash复制find /data/projects -type f \( -name "*.log" -o -name "*.out" \) -size +100M | cpio -o > big_logs.cpio
因为 find 的输出已经精确到了每一个文件,cpio 不需要再做任何过滤判断,直接归档即可。这就是 cpio 面向文件流设计的最大价值。
如果你需要将归档压缩,依然可以在管道里加 gzip:
bash复制find /data/projects -name "*.log" | cpio -o | gzip > logs_backup.cpio.gz
恢复时,还是 cd 到目标目录,然后执行:
bash复制cd /data/restore
zcat ../logs_backup.cpio.gz | cpio -idmu
我加的 -u 参数在这里是有意为之:批量恢复时,允许 cpio 覆盖目标目录中已存在的同名文件。如果漏了 -u,再遇到同名文件时 cpio 会跳过,造成恢复不完整。
5.2 用 copy-pass 模式做目录精确同步
copy-pass 模式是 cpio 里最容易被忽视的模式,但它在一个场景下非常好用:把源目录中符合某种条件的文件"原地"复制到目标目录,同时保留完整目录结构。
例如,我想把 /etc 下所有 .conf 文件复制到 /opt/config_backup,同时保留 /etc 这个目录结构层次:
bash复制cd /
find etc -name "*.conf" -depth | cpio -pdm /opt/config_backup
这里注意我已经先 cd / 了,所以 find 搜的是相对路径 etc。如果不写前面的 cd /,直接 find /etc,那么最终结果会在 /opt/config_backup 下面再出现一个 /etc 目录,多包一层。在写脚本时,这个差异有时候是符合预期的,有时候会让人摸不着头脑。我建议每次写这种命令前先自己推演一遍最终路径结构,不要直接运行了事。
copy-pass 模式还有一个隐蔽的优势:它不生成中间归档,因此对磁盘空间没有额外占用。大目录复制时,如果归档方案需要临时占用几十 GB 的空间,copy-pass 模式可以把这个开销降到零。
5.3 备份恢复时的安全技巧
用 cpio 恢复文件时,我推荐养成几个习惯。第一,恢复前先列出归档内容确认路径结构:
bash复制cpio -it < backup.cpio | head -30
第二,恢复到临时目录而不是直接覆盖生产目录。先将归档解压到 /opt/restore_tmp,确认文件无误后再使用。第三,保留原有的属主和权限信息。cpio 默认会保存这些信息,但如果你想完整还原包括时间戳在内的所有元数据,必须加上 -m 参数,这样恢复出的文件修改时间才和归档时完全一致。
6. 常见问题与排查技巧实录
6.1 cpio 报错 "Not a cpio file"
这个问题在三种情况下最常出现。一是你用 zcat 解压 .gz 文件后没有用管道给 cpio,而是直接把 .gz 文件当普通文件给 cpio 读;二是文件格式不是 cpio 而是 tar 或者其他格式;三是文件在传输过程中有损坏。排查方式是从简单到复杂逐层验证:
bash复制file backup.cpio.gz
file backup.cpio
file 命令会告诉你真实文件类型。如果显示 gzip compressed data,那么下一步就该用 zcat 解压。如果显示 ASCII cpio archive,那说明你没有加 -H 参数指定格式,直接换个解包方式就行。
6.2 解包后目录结构不对
如果你解包后发现文件全部平铺在当前目录,没有任何目录层级,最大的可能是你原来的归档就是用相对路径或纯文件名创建的,而不是带目录结构。在打包时如果想保留路径结构,find 和 cpio 的路径参数就必须统一。你 find /data -type f | cpio -o 打包出来的归档内部是 /data/xxx 这种绝对路径结构;而你 find data -type f | cpio -o 打包出来就是 data/xxx 这种相对路径。解包时,建议 cd 到合适的根目录再解包,这样最终的落盘位置就在预期范围里。
6.3 解包时提示 "cpio: Cannot chown()"
这个错误通常不是因为你的命令有问题,而是当前用户权限不足。cpio 在恢复文件时默认会尝试恢复文件属主和组,非 root 用户执行这条操作会失败。如果你只是临时解包看看文件内容,加上 --no-preserve-owner 参数或者在命令中加入 --no-absolute-filenames 时顺带避免属主恢复,就能正常解包。但如果你是 root 用户执行却出现这个问题,那很可能是文件系统挂载选项有关,比如挂载时指定了 nosuid 或类似的安全选项,影响了权限属性的恢复。这时候就要检查目标目录所在文件系统的挂载参数了。
6.4 制作 initramfs 时出现的常见错误
手工制作 initramfs 时最容易遇到的问题是打包路径错误。如果你没有先 cd 到 rootfs 目录里执行 find .,而是直接在某处执行 find /path/to/rootfs,打包出来的归档内容会带着完整的绝对路径,内核加载后找不到 init 程序,启动必然会失败。排查时,先用 cpio -it 打开你的 initramfs 镜像,看看路径是不是以 . 开头的相对路径:
bash复制zcat initramfs.img | cpio -it | head
如果第一行显示的是 ./bin/busybox 而不是 bin/busybox,也别紧张,内核其实也能识别带 . 开头的路径。真正的问题是路径变成 /path/to/rootfs/bin/busybox 这种绝对路径,那就基本没救了,必须重新打包。
6.5 cpio 和 tar 在管道使用上的核心差异
很多经验丰富的运维也会混淆这一点。tar 使用 -C 参数切目录,而 cpio 没有等价的全局"切目录"参数,它的一切行为都跟当前工作目录和输入路径有关。在脚本里使用 cpio 之前,最好显式 cd 到目标目录。这不是限制,是提醒:cpio 更贴近"底层工具"的定位,它需要你更准确地表达自己的意图。
我来整理一个快速对比表,帮大家直观感受 cpio 和 tar 的侧重点差异:
| 对比项 | tar | cpio |
|---|---|---|
| 设计哲学 | 面向文件树,直接处理目录 | 面向文件流,通过管道接收文件列表 |
| 压缩支持 | 内置 -z/-j 参数 | 无内置压缩,用管道配合 gzip 等程序 |
| 递归目录 | 默认递归 | 本身不递归,依赖 find 列出路径 |
| RPM 包提取 | 不支持 | rpm2cpio 配合后可提取 |
| initramfs 制作 | 不适用 | 标准方案 |
| 精确备份 | 较繁琐 | 天然优势 |
看完这个表你就明白了,tar 是"全能选手",cpio 是"特种兵"。日常打包分发用 tar,需要精确处理文件流时用 cpio,两边并不冲突。真正的高手不是只把 tar 用到极致,而是知道在什么场景下换哪把工具。
7. 几个容易被忽视的高级技巧
7.1 用 cpio 实现极简的远程备份
cpio 的流式特性让它天然适合远程备份。你不需要先在本机生成归档文件再上传,而是可以直接通过 SSH 把标准输出推送到远程主机:
bash复制find /data -type f | cpio -o | ssh user@backup-server "cat > /backups/data.cpio"
同理,远程恢复也可以这样完成:
bash复制ssh user@backup-server "cat /backups/data.cpio" | cpio -idm
这种方式在带宽有限、磁盘空间紧张的环境下非常实用,全程没有中间文件产生。我在给一些旧设备的现场做数据迁移时,这套方式帮我省了很多事。
7.2 处理文件名中的特殊字符
传统 cpio 对文件名中包含换行符的情况比较敏感,因为 find 默认按行输出路径,遇到换行符会断行。现代 GNU find 和 cpio 其实已经支持通过 -print0 参数配合 -0 参数来处理这种场景:
bash复制find /data -print0 | cpio -0 -o > backup.cpio
不过说实话,绝大多数场景用不到这么极端的处理方式。我写过这个,主要是因为面试和规范文档里偶尔会刁钻地问到,知道有这回事就行。
7.3 跨平台交换时的格式选择
如果你需要把一个 cpio 归档交给 Solaris、AIX 或者其他 Unix 系统处理,格式选择会直接影响兼容性。建议统一使用 -H odc 格式:
bash复制find /data -type f | cpio -o -H odc > backup_portable.cpio
odc 格式是纯 ASCII 的 POSIX 可移植格式,只要目标平台支持 POSIX 标准,解析就没有压力。我自己在实际工作中维护过一台老旧的 Sun 服务器,所有备份都用这个格式,从来没有因为格式问题翻过车。
8. 我对 cpio 使用的几点切身体会
这些东西我在前面分散提到了一些,但这里专门归拢起来再说一遍。首先,cpio 不是一个"好看的"命令,它的参数和架构都比较老派,初学时会有一种"我在用上世纪工具"的错觉。但它能在 Unix 世界里存活这么多年,必然有其不可替代的价值。特别是在精确文件选择、管道配合、RPM 包处理和 initramfs 制作这四件事上,cpio 一直是我的第一选择。
其次,如果你在公司里接手了老项目,看到脚本里出现了 find ... | cpio 这样的写法,先别急着把它改成 tar。在充分理解业务逻辑之前,贸然替换工具可能导致之前依赖的某些行为发生变化,比如时间戳保留策略、覆盖规则、路径结构等。cpio 的很多细节行为都和 tar 不同,有些老脚本的价值恰恰就在于它依赖这些细节。
最后,花十几分钟在测试环境里亲手跑一遍 cpio 的三种模式,比背十遍参数表都管用。你可以像我一样,创建一个 test 目录,放上几个不同层级的文件,分别试一下 copy-out、copy-in 和 copy-pass,再看一下带 -d 和不带 -d 的区别。你很快就能理解为什么我会说 cpio 是"一个值得敬畏的老前辈"。
