Skip to content

xargs 与并行执行 ​

| 传的是标准输入,不是参数。很多命令只吃参数,所以需要 xargs 做转换。文件名带空格时,不加 -0 会直接把命令拆坏。

一、为什么管道会"炸" ​

bash
# 错:rm 不读 stdin,什么都不会删
find /data/logs -name '*.log' | rm -f

# 对
find /data/logs -name '*.log' -print0 | xargs -0 rm -f

xargs 做的事:把 stdin 按空白拆成参数,追加到命令后面。

bash
echo 'a b c' | xargs touch
ls a b c
text
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 echo
text
1 2
3 4
5 6
bash
# -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 echo
text
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 -l
text
# 等价于
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' _

九、踩过的坑 ​

现象根因处置
带空格的文件被拆成两个参数没用 -0find -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