Shell 归档压缩与效率命令
更新时间:2026-08-28。本文是 Shell 编程主题第⑦族:归档压缩与效率工具,共 20 个命令,也是本主题的收尾篇。
与 Linux 命令速查第⑧族 的分工:那边是字典式速查(34 个命令,含
zstd、pigz、pax、7z等);本文把最常用的 20 个讲透,重点是怎么写一个真正能用的备份脚本,以及那些能显著提升日常效率的小技巧。
一、打包与压缩
1.1 tar
先分清两个概念:归档(把一堆文件打包成一个,不改变大小)和压缩(真正减小体积)。tar 只做归档,配合 gzip/xz 等工具才同时完成两件事。
参数速记:c 创建、x 解包、t 查看、v 显示过程、f 指定文件名、z gzip、j bzip2、J xz。f 必须放在最后且紧跟文件名——tar -czf file.tar.gz 不能写成 tar -cfz,因为 -f 后面会被当作文件名。
几个容易踩的坑:一是解压前先 tar -tzf 看内容,有些包解压后会散落一堆文件在当前目录("tar bomb"),有些则带一层目录,提前知道能避免把目录弄乱;二是用 -C 指定解压目标目录(tar -xzf a.tar.gz -C /target/);三是打包时配合 -C 切到父目录再打包,能让归档内不含绝对路径(tar -czf a.tar.gz -C /data app),解压时不会覆盖到意外的地方;四是 --exclude 排除文件(可多次使用,也支持 --exclude-from=file)。
增量与追加:-r 往已有归档里追加文件(不能用于压缩过的归档),-u 只追加比归档内更新的文件,--newer 按时间筛选。差分备份用 --listed-incremental。
tar -czf a.tar.gz dir/ # 打包 + gzip 压缩
tar -xzf a.tar.gz # 解压
tar -tzf a.tar.gz # 先看内容(解压前必做)
tar -xzf a.tar.gz -C /target/ # 解压到指定目录
tar -czf a.tar.gz -C /data app # 归档内不含绝对路径
tar -czf a.tgz --exclude='*.log' --exclude='node_modules' dir/
tar -xzf a.tar.gz path/to/file # 只提取某个文件
tar -cjf a.tar.bz2 dir/ # bzip2
tar -cJf a.tar.xz dir/ # xz(压缩率最高)
tar -rvf a.tar newfile # 追加(仅限未压缩归档)1.2 gzip / bzip2 / xz — 单文件压缩
gzip 默认会删除原文件(生成 file.gz),-k 保留原文件,-d 解压,-1~-9 调压缩级别(-1 最快、-9 最小,默认 -6),-c 输出到标准输出(配合重定向可以保留原文件:gzip -c f > f.gz)。
配套的 zcat/zless/zgrep 是查历史日志的利器——不用先解压就能查看和搜索 gzipped 内容,处理轮转后的历史日志时省掉大量步骤。
选压缩算法的本质是在压缩率和速度之间权衡:gzip 最快、兼容性最好;xz 压缩率最高但压缩很慢很耗内存,适合归档冷数据;bzip2 介于两者之间但已逐渐被取代。现在还有更现代的选择:zstd(压缩率接近 xz 但速度快一个量级,解压极快,正在成为新标准)和 lz4(极速,压缩率一般,主打"几乎不影响性能")。日常打包推荐 zstd,追求兼容用 gzip。
gzip file # 压缩(默认删原文件)
gzip -k file # 保留原文件
gzip -9 file # 最大压缩
gzip -d file.gz # 解压
zcat file.gz # 不解压查看
zgrep "error" *.gz # 不解压搜索(查历史日志必备)
zless file.gz # 不解压分页看
xz -k file # xz 压缩(压缩率最高)
xz -T0 file # 多线程压缩
zstd file # zstd(快且小,推荐)
zstd -19 file # 高压缩比档1.3 zip / unzip — 跨平台格式
与 Windows 或其他系统交换文件时,zip 是最保险的选择(双方都原生支持,而 tar.gz 在 Windows 上需要额外工具)。zip -r 递归打包目录,-e 交互式设密码,-x 排除文件,-q 静默。unzip -l 先看内容再解压,-d 指定解压目录,-o 覆盖不提示,-O gbk 解决中文文件名乱码(Windows 生成的 zip 里中文文件名常用 GBK 编码,Linux 下解压会乱码)。
zip -P 的加密是弱加密,很容易被破解,别用它保护真正敏感的数据;需要加密请用 7z(AES-256)或 gpg,见 安全加密与审计。
zip -r archive.zip dir/ # 递归打包
zip -r a.zip dir/ -x '*.log' # 排除文件
unzip -l archive.zip # 先看内容
unzip archive.zip -d out/ # 解压到指定目录
unzip -O gbk win.zip # 解决中文文件名乱码
unzip -o archive.zip # 覆盖不提示二、时间与执行控制
2.1 date
date 是脚本里生成时间戳的主力。格式化用 + 开头:%Y 年、%m 月、%d 日、%H 时、%M 分、%S 秒、%F 等价于 %Y-%m-%d、%T 等价于 %H:%M:%S、%s Unix 时间戳、%u 星期几(1~7)。
-d 做时间运算非常实用:date -d yesterday、date -d "7 days ago"、date -d "next monday"、date -d @1700000000(时间戳转可读)。日志清理、备份命名、按天归档都靠它。备份脚本里最常见的一行就是 DATE=$(date +%F),然后所有文件名都带上这个日期。
date # 当前时间
date '+%F %T' # 2026-08-28 10:30:45
date '+%Y%m%d_%H%M%S' # 20260828_103045(文件名用,避免空格冒号)
date +%s # Unix 时间戳
date -d "yesterday" '+%F' # 昨天
date -d "7 days ago" '+%F' # 7 天前(清理旧文件用)
date -d "next monday" '+%F' # 下周一
date -d @1700000000 # 时间戳转可读2.2 time / sleep / wait
time 测命令耗时,输出三个值:real 是墙上时钟时间(你感受到的耗时)、user 是用户态 CPU 时间、sys 是内核态 CPU 时间。这三个值的关系能告诉你瓶颈在哪:多核并行时 user+sys 可能远大于 real;反过来如果 real 远大于 user+sys,说明大量时间在等待(I/O 或锁),而不是在计算。注意 time 是 Shell 关键字,重定向时要小心(time cmd > file 只会重定向 cmd 的输出);要测更详细的资源占用,用 /usr/bin/time -v(需单独安装)。
sleep 支持小数和单位后缀:sleep 5、sleep 0.5、sleep 1m、sleep 2h。轮询脚本和重试逻辑里离不开它。wait 等待后台任务结束:wait 等所有、wait $PID 等指定进程,配合 & 做并发控制(起一批后台任务,最后 wait 统一收口)。
time command # 测耗时(real/user/sys)
time curl -s URL >/dev/null # 测接口耗时
/usr/bin/time -v cmd # 详细资源统计
sleep 5 # 等 5 秒
sleep 0.5 # 支持小数
sleep 1m # 支持单位后缀
# 并发执行后统一等待
for host in host1 host2 host3; do
check_host "$host" &
done
wait # 等所有后台任务结束2.3 timeout 与 watch
timeout 限时运行命令,超时自动结束——脚本里调用外部程序时非常有用,能防止某个命令卡死拖垮整个流程。timeout 10s cmd 给 10 秒;-k 5 表示先发 TERM、再等 5 秒后发 KILL(对付不响应 TERM 的顽固进程);--signal 指定发送的信号;--preserve-status 让 timeout 返回被超时命令自己的退出码(而不是固定的 124)。调用外部 API、远程探测、任何不受控的操作都应该套上它——这是脚本健壮性的基本保障。
watch 周期性执行某个命令并全屏刷新。watch -n 2 df -h 每 2 秒刷新一次磁盘;-d 高亮与上一次不同的部分(盯某个数值变化时特别直观);-t 不显示顶部标题栏(输出更干净,便于复制)。它适合临时监控,长期采集应该用 sar 这类专门工具。
timeout 10s ./task # 10 秒超时
timeout -k 5 30 cmd # 30 秒后 TERM,再 5 秒后 KILL
timeout --signal=INT 5 cmd # 发 INT 而非默认的 TERM
timeout --preserve-status 10 cmd # 保留原命令的退出码
watch -n 2 df -h # 每 2 秒刷新
watch -d -n 1 'ss -s' # 高亮变化部分三、命令查找与别名
3.1 which / whereis / type
三者都能"找命令",但给出的信息不同:which 在 $PATH 里找可执行文件的位置(-a 列出所有匹配而不只是第一个);whereis 找二进制、源码和手册页三个位置(-b 只要二进制);type 是 Shell 内建命令,能告诉你这个命令到底是别名、内建命令还是外部程序(type -a ls 会列出所有同名,包括别名)。
脚本里判断命令是否存在,推荐用 command -v 而不是 which——后者不是内建命令,不同系统行为不一致,且在某些环境下输出格式不稳定。command -v cmd >/dev/null 2>&1 && echo 存在 是标准写法。
which python3 # 在 PATH 里找
which -a python # 列出所有匹配
whereis ls # 二进制 + 源码 + 手册页
type -a ls # 类型(别名/内建/外部)+ 所有位置
command -v docker >/dev/null && echo "docker 已安装" # 脚本判断推荐写法3.2 alias — 命令别名
alias 把长命令缩短,是最直接的提效手段。alias 不带参数列出所有别名,unalias 删除。别名只在当前 Shell 会话有效,写进 ~/.bashrc(或 ~/.zshrc)才能永久生效,改完要 source ~/.bashrc 重新加载。
注意别名在非交互 Shell(脚本)里默认不展开,所以脚本里应该用函数而不是别名。另外别名可能造成困扰——alias rm='rm -i' 是好习惯,但如果习惯了它,到别的机器上就可能误删。常见的实用别名包括 ll='ls -lh'、grep='grep --color=auto'。
alias ll='ls -alF' # 定义别名
alias grep='grep --color=auto'
alias # 列出所有别名
unalias ll # 删除
echo "alias ll='ls -alF'" >> ~/.bashrc # 永久生效
source ~/.bashrc # 重新加载3.3 hash
Shell 会缓存已执行命令的路径,避免每次都搜 $PATH。hash 显示这个缓存,-r 清空。当你安装了新版本的程序、或改动了 $PATH 的顺序,却发现系统还在调用旧版本时,就是缓存没清——hash -r 一下即可。-d name 只删某一项。
hash # 查看命令路径缓存
hash -r # 清空(装了新版程序后调用到旧版时执行)
hash -d python # 只清某一项四、历史与输入效率
4.1 history
history 列出命令历史;history N 只显示最近 N 条。历史展开:!! 重跑上一条,!N 重跑第 N 条,!grep 重跑最近以 grep 开头的命令,!$ 引用上一条命令的最后一个参数(比如 mkdir /a/b/c 之后 cd !$ 就等于 cd /a/b/c),!* 引用上一条命令的所有参数。
不过最高频的用法其实是 Ctrl+R:按一下输入片段,就能反向搜索历史命令,比翻 history 快得多。找到后回车执行,或按右箭头编辑后再执行。这条快捷键是命令行效率的分水岭。
历史相关的环境变量:HISTSIZE(内存中保留条数)、HISTFILESIZE(历史文件保留条数)、HISTCONTROL=ignoredups(忽略连续重复)、HISTTIMEFORMAT(给每条历史加时间戳,方便回溯"我什么时候执行过这个")。
history # 全部历史
history 20 # 最近 20 条
history | grep rsync # 找历史里用过的命令
!! # 重跑上一条
!123 # 重跑第 123 条
!$ # 上一条命令的最后一个参数
Ctrl+R # 反向搜索历史(最实用)4.2 键盘快捷键
这些是纯肌肉记忆,但收益极高:
| 按键 | 作用 |
|---|---|
Ctrl+R | 反向搜索历史(最实用) |
Ctrl+A / Ctrl+E | 光标跳到行首 / 行尾 |
Ctrl+U / Ctrl+K | 删除光标前 / 后的所有内容 |
Ctrl+W | 删除光标前的一个单词 |
Ctrl+L | 清屏(等同 clear) |
Ctrl+C | 中断当前命令 |
Ctrl+Z | 挂起当前命令(fg 恢复) |
Tab | 自动补全(连按两次列出候选) |
五、帮助与手册
man 是最权威的本地文档,不需要联网。它分章节:1 是用户命令、2 是系统调用、3 是库函数、5 是文件格式、8 是管理命令。所以查 open 要写 man 2 open(系统调用)而不是 man 1 open。man -k keyword 按关键字搜索手册(等价 apropos,忘了命令名时用),man -f cmd 显示一句话说明。
看 man 的技巧:/关键词 搜索、n/N 跳到下一个/上一个匹配、q 退出、空格翻页。--help 给出简短的选项列表(比 man 快,适合只想知道参数);info 是 GNU 的文档系统,比 man 更详细且带超链接;whatis 一句话说明命令是干什么的。
不想读长篇手册时,tldr 是更好的选择——它直接给出常用例子,见 现代命令行工具。
man ls # 完整手册
man 2 open # 指定章节(系统调用)
man -k "copy file" # 按关键字搜索(忘了命令名时用)
man -f ls # 一句话说明
ls --help # 简短选项列表
info coreutils # GNU info 文档
whatis ls # 一句话说明六、杂项实用命令
tee 分流输出(既看又存),在长管道中间取样调试时很好用,配合 sudo 还能写入特权文件(echo x | sudo tee /etc/file)。完整用法见 文本处理。
环境变量相关:echo $? 看上一条命令的退出码(0 成功,非 0 失败,脚本里判断成败的基础);export VAR=value 导出环境变量(子进程可见);env 查看所有环境变量,或 env -i cmd 在干净环境下运行(排查环境依赖);set 查看所有 Shell 变量(含未导出的)。这几个的完整说明见 Shell 内建命令速查。
十六进制查看:xxd file 转储十六进制(-r 可反向还原,编辑二进制时用),od -c file 按字符显示(能看见 \r、\n 这类不可见字符,排查换行符问题必备),od -A x -t x1z file 是"十六进制 + ASCII"双栏显示,最常用。
echo $? # 上一条命令的退出码
export PATH=$PATH:/opt/bin # 导出环境变量
env # 查看环境变量
env -i ./app # 干净环境下运行(排查环境依赖)
set # 查看所有 Shell 变量
cmd | tee out.log # 输出到屏幕同时存文件
echo x | sudo tee /etc/file # 写入需要 root 的文件
xxd file | head # 十六进制转储
od -c file | head # 看不可见字符(找 ^M)
od -A x -t x1z file # hex + ASCII 双栏七、脚本调试与质量
写脚本和写别的代码一样,需要调试手段和质量保障。Bash 提供了几个内建机制,配合外部工具能覆盖大部分需求。
bash -n script.sh 是纯语法检查——不执行脚本,只检查语法错误。改完脚本先跑一下它,能挡掉括号不匹配、then 缺失这类低级错误,比直接运行快得多。
set -x 开启执行跟踪,把每条实际执行的命令(变量已展开)打印到 stderr。脚本行为不符合预期时,在可疑段落前后加 set -x / set +x,就能看到到底执行了什么。它是定位"变量没按我想的展开"这类问题的最快手段,比到处 echo 干净。想全局开启就在脚本开头写 set -x,或在运行时 bash -x script.sh。
set -euo pipefail 是脚本开头的黄金三件套:-e 让任何命令失败时脚本立即退出(而不是带着错误状态继续跑,把事情搞得更糟);-u 遇到未定义变量就报错(避免空变量导致的 rm -rf 事故);-o pipefail 让管道的返回值是最后一个失败的命令的退出码,而不是默认的最后一个命令(否则 grep x file | wc -l 里 grep 失败也会被当成成功)。
shellcheck 是 Shell 脚本的静态检查工具(需单独安装),能发现大量常见陷阱:没加引号的变量、误用 [ 与 [[、cd 后没检查失败、echo 的移植性问题等。它的建议会给出具体原因和修复方案。把 shellcheck script.sh 加进提交前的检查流程,能显著提升脚本质量。
bash -n script.sh # 语法检查(不执行)
bash -x script.sh # 执行跟踪
set -euo pipefail # 脚本开头三件套(必备)
set -x / set +x # 局部开启/关闭跟踪
shellcheck script.sh # 静态检查,发现常见陷阱八、脚本实战:一个能用的备份脚本
备份这件事的难点从来不是"怎么打包",而是"打包之后怎么管理"。下面逐步搭一个能真正投入使用的备份脚本。
8.1 最简备份:按日期归档
#!/usr/bin/env bash
set -euo pipefail
SRC=${1:?用法: backup.sh <源目录>}
DST=/backup
DATE=$(date +%Y%m%d_%H%M%S)
NAME=$(basename "$SRC")
mkdir -p "$DST"
tar -czf "$DST/${NAME}_${DATE}.tar.gz" -C "$(dirname "$SRC")" "$NAME"
echo "备份完成: $DST/${NAME}_${DATE}.tar.gz"${1:?提示} 是 Bash 的参数校验写法:参数没给就直接报错退出并打印提示,不用自己写 if 判断。tar 里用 -C 切到父目录再打包,能让归档内不含绝对路径——解压时不会覆盖到意外的地方。
8.2 自动清理旧备份
# 保留最近 7 份,删掉更早的
ls -t "$DST"/${NAME}_*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm -f
# 或者按时间清理
find "$DST" -name "${NAME}_*.tar.gz" -mtime +7 -delete只备份不清理,磁盘迟早会满——这是备份脚本最常见的事故。清理策略要写进脚本本身,别指望有人记得手动删。xargs -r 的作用:没有匹配时不执行 rm,避免意外。
8.3 完整性校验
tar -czf "$DST/${NAME}_${DATE}.tar.gz" -C "$(dirname "$SRC")" "$NAME"
sha256sum "$DST/${NAME}_${DATE}.tar.gz" >> "$DST/SHA256SUMS"
# 恢复前先验证
sha256sum -c "$DST/SHA256SUMS" 2>/dev/null | grep -v ': OK$' && {
echo "校验失败,备份文件已损坏!" >&2
exit 1
}校验和的意义在于发现损坏,而不是防止损坏。备份文件放久了可能位衰减,传输过程可能出错,恢复时才发现坏了就太晚了。
8.4 数据库备份要额外注意
# MySQL:先 dump 成文本再压缩(不要直接 tar 数据目录)
mysqldump -u root -p"$DBPASS" --single-transaction --routines "$DBNAME" \
| gzip > "$DST/db_${DBNAME}_${DATE}.sql.gz"
# 校验 dump 文件是否正常
zcat "$DST/db_${DBNAME}_${DATE}.sql.gz" | head -20不要直接 tar 数据库的数据目录——备份过程中数据库还在写,拿到的快照很可能不一致,恢复时才发现表损坏。mysqldump --single-transaction 能拿到一致的逻辑备份,虽慢但可靠。
8.5 异地备份与恢复演练
# 同步到远端(至少有一份不在本机)
rsync -avz -e "ssh -p 22" "$DST/" backup@remote:/backup/offsite/
# 恢复演练:定期验证备份真的能解开
tar -tzf "$DST/${NAME}_${DATE}.tar.gz" >/dev/null && echo "归档结构正常"
mkdir -p /tmp/restore_test && tar -xzf "$DST/${NAME}_${DATE}.tar.gz" -C /tmp/restore_test
diff -r "$SRC" "/tmp/restore_test/$NAME" && echo "内容一致,备份可用"
rm -rf /tmp/restore_test没有验证过的备份等于没有备份。至少每季度抽一次,真的把备份解开、比对内容——只有恢复成功过,你才能确定这套备份是有效的。
九、与主线衔接
- 归档与 文件与目录命令 的
find配合,能做增量与定期清理。 - 备份脚本常作为定时任务运行,衔接 日志、时间与计划任务。
- 压缩算法、zip/7z、校验和的完整参数见 Linux 命令速查第⑧族。
- 需要加密备份时用
gpg,见 安全加密与审计。
一句话总结
归档压缩与效率 = 打包(tar)+ 压缩(gzip/xz/zip,新项目优先 zstd)+ 时间(date)+ 控制(timeout/wait)+ 效率(alias/Ctrl+R);脚本质量靠 set -euo pipefail 与 shellcheck 保障;备份脚本额外记住四条——只备份不清理磁盘必满、备份即算 sha256sum、数据库用 dump 而非直接打包数据目录、定期做恢复演练。