外观
xargs 与并行执行
| 传的是标准输入,不是参数。很多命令只吃参数,所以需要 xargs 做转换。文件名带空格时,不加 -0 会直接把命令拆坏。
一、为什么管道会"炸"
bash
# 错:rm 不读 stdin,什么都不会删
find /data/logs -name '*.log' | rm -f
# 对
find /data/logs -name '*.log' -print0 | xargs -0 rm -fxargs 做的事:把 stdin 按空白拆成参数,追加到命令后面。
bash
echo 'a b c' | xargs touch
ls a b ctext
a b c文件名里有空格时
默认 xargs 按空格和换行切分,文件 my file.log 会被当成两个参数 my 和 file.log。
bash
# 必须用 -0 配合 find -print0
find /data -name '*.log' -print0 | xargs -0 rm -f其他会产生 -0 输入的:grep -lZ、git ls-files -z。
二、常用参数
bash
# -n 每次传几个参数
echo 1 2 3 4 5 6 | xargs -n 2 echotext
1 2
3 4
5 6bash
# -I 占位符(一次一个,可放到命令中间)
find /data -name '*.log' | xargs -I{} mv {} {}.bak
# -t 打印要执行的命令
find /data -name '*.tmp' -print0 | xargs -0 -t rm -f
# -r 没有输入时不执行(GNU 特性,建议总是加)
find /data/empty -name '*.log' | xargs -r rm -f
# -d 自定义分隔符
echo 'a,b,c' | xargs -d, -n1 echotext
rm -f /data/a.tmp
rm -f /data/b.tmp-I{} 隐含 -n1 和"不按空白切分",但性能比 -n 差(每个文件起一个进程)。能用 -n 就别用 -I。
三、并行执行
bash
find /data/logs -name '*.gz' -print0 \
| xargs -0 -n1 -P4 gzip -d-P4 同时跑 4 个进程。-P0 表示不限(按 CPU 核数,慎用)。
耗时对比(1000 个文件,每个 1 秒):
| 方式 | 耗时 |
|---|---|
| 串行 | ~1000s |
-P4 | ~250s |
-P8 | ~125s |
-P16 | ~65s(IO 成为瓶颈) |
-P 不是越大越好
-P 开太大时,磁盘 IO、网络带宽、CPU 都会成为瓶颈,反而更慢,还可能把目标服务打挂(比如并行压缩 500 个日志把磁盘打满)。
经验值:CPU 密集型取核数,IO 密集型取 2~4 倍核数。
四、典型场景
批量删除旧日志
bash
find /data/logs -type f -name '*.log' -mtime +30 -print0 \
| xargs -0 -r rm -f先 -print 看清单再删更稳:
bash
find /data/logs -type f -name '*.log' -mtime +30 -print | head -20批量替换配置
bash
grep -rlZ 'old.host.com' /etc/app \
| xargs -0 -r sed -i 's/old.host.com/new.host.com/g'grep -l 只输出文件名,-Z 用 \0 分隔,配合 xargs -0。
批量打包
bash
ls /data/releases | xargs -I{} tar -czf /data/backup/{}.tar.gz -C /data/releases {}统计多个文件的行数
bash
find /var/log -name '*.log' -print0 | xargs -0 -n1 wc -l | tail -1并行执行远程命令
bash
cat hosts.txt | xargs -n1 -P5 -I{} ssh {} 'systemctl restart nginx'五、xargs 的"位置参数"
默认 xargs 把参数追加到命令行末尾:
bash
echo /data/app | xargs ls -ltext
# 等价于
ls -l /data/app但有些命令要求参数在中间,必须用 -I:
bash
# 错:cp /data/app /data/backup
echo /data/app | xargs cp /data/backup
# 对
echo /data/app | xargs -I{} cp {} /data/backup/六、一个更安全的替代:find -exec
bash
# 每个文件一次
find /data -name '*.log' -exec gzip {} \;
# 攒起来一次传多个(相当于 xargs,且天然处理空格)
find /data -name '*.log' -exec gzip {} +-exec {} + 内部用 execvp 传数组,不经过 shell 切分,所以文件名带空格也安全,不需要 -0。
什么时候仍用 xargs:需要 -P 并行、或者输入不是来自 find。
七、并行工具对比
| 工具 | 特点 |
|---|---|
xargs -P | 系统自带,够用 |
GNU parallel | 功能强(重试、超时、进度、多机) |
parallel --bar | 带进度条 |
bash
# GNU parallel
find /data -name '*.log' | parallel -j4 gzip {}
find /data -name '*.log' | parallel --bar -j4 'gzip {} && echo done {}'parallel 需要额外安装,但默认按行切分且不拆空格(更安全),还有 --timeout、--retries。
八、退出码
bash
find /data -name '*.log' | xargs -r gzip
echo $?xargs 的退出码:
| 码 | 含义 |
|---|---|
| 0 | 全部成功 |
| 123 | 有子命令退出码在 1~125 之间 |
| 124 | 子命令被信号杀掉 |
| 125 | 命令被 xargs 自己杀掉 |
| 126 | 命令无法执行 |
| 127 | 命令不存在 |
想让"任意一个失败就停":
bash
find /data -name '*.log' | xargs -r -n1 sh -c 'gzip "$1" || exit 255' _九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| 带空格的文件被拆成两个参数 | 没用 -0 | find -print0 | xargs -0 |
| 没有输入时命令照样执行 | 默认会跑一次空的 | 加 -r |
| 参数顺序不对 | xargs 只能追加到末尾 | 用 -I{} |
-I 很慢 | 每个参数起一个进程 | 改 -n + 脚本 |
-P 开了但没变快 | 瓶颈在 IO/网络 | 减小 -P |
命令里有 {} 被当成占位符 | 与 -I{} 冲突 | 换占位符如 -I@ |
Argument list too long | 一次传太多参数 | xargs 会自动分批,或调 -n |
xargs 报了错但退出码是 0 | 用的是老版本/BSD | 检查 echo $?,显式加 || exit 1 |