xargs —— 把 stdin 喂给下一个命令
一句话定义
xargs 读 stdin,把内容当成参数喂给指定命令运行。它解决一个核心问题:很多命令(rm、mv、grep、kubectl)通过参数接收文件,但前一步的命令(find、ls、cat)通过 stdout 输出文件列表。xargs 是中间的桥。
典型场景
find . -name "*.log" | xargs rmcat ips.txt | xargs -I {} ssh {} 'uptime'- 并行:
cat hosts | xargs -P 10 -I {} ssh {} 'command' - 批量 docker / kubectl 操作
- 解决"argument list too long"
基础:xargs 和管道的区别
echo "file1 file2" | rm # ❌ rm 不读 stdin
echo "file1 file2" | xargs rm # ✅ xargs 把 stdin 转成参数:rm file1 file2
xargs 默认把 stdin 用空白(空格 / 换行)分隔成多个参数:
echo "a b c" | xargs echo
# echo a b c
# a b c
5 个核心 flag
xargs -n 1 # 每次只传 1 个参数
xargs -I {} # 用 {} 占位(位置自定义)
xargs -P 10 # 并行 10 个
xargs -0 # 用 NULL 分隔(处理含空格的文件名)
xargs -p # prompt 每个命令确认
-I {} —— 占位符模式(最常用)
cat hosts.txt | xargs -I {} ssh {} 'uptime'
# 每行一个 host,对每个跑 ssh xx uptime
# 经典 K8s 套路:删所有 pending pod
kubectl get pods -A | grep Pending | awk '{print $1, $2}' \
| xargs -I {} bash -c 'set -- {}; kubectl delete pod $2 -n $1'
-I {} 之后,{} 在命令里代表当前 stdin 行。
没
-I时 xargs 默认追加到命令末尾、一次喂多个。有-I时每次喂一个、放占位符位置。
-n N —— 每次喂 N 个参数
echo "1 2 3 4 5 6" | xargs -n 2 echo
# echo 1 2
# echo 3 4
# echo 5 6
# 一次删 100 个 pod(避免 kubectl 命令行过长)
kubectl get pods -o name | xargs -n 100 kubectl delete
-P N —— 并行(杀手锏)
cat hosts | xargs -P 10 -I {} ssh {} 'apt-get update -qq'
# 同时 10 个 ssh 并发
# 批量 ping 测试
seq 1 254 | xargs -P 50 -I {} ping -c 1 -W 1 192.168.1.{}
-P 让 xargs 起 N 个并行进程。运维批量任务标配。
注意输出可能交错。要保留顺序:
xargs -P 10 -I {} sh -c 'output=$(do_stuff {}); echo "$output"'。
-0 —— 处理含空格 / 特殊字符的文件名
find . -name "*.log" | xargs rm
# 文件 "a b.log" → xargs 切成 "a" 和 "b.log"
find . -name "*.log" -print0 | xargs -0 rm
# -print0 用 NULL 分隔 + xargs -0 也用 NULL → 安全处理任何字符
含空格的文件名场景必加 -0。
看会跑啥(--verbose / -t)
echo "1 2 3" | xargs -n 1 -t echo
# echo 1
# 1
# echo 2
# 2
# ...
-t 让 xargs 在每次执行前打印命令。调试 xargs 必用。
实用 flag 速查
| flag | 作用 |
|---|---|
-I {} | 占位符(最常用) |
-n N | 每次传 N 个参数 |
-P N | 并行 N 个进程 |
-0 | NULL 分隔(处理空格) |
-t | 显示要跑的命令(调试) |
-p | 每次确认 |
-r | stdin 空就不跑(脚本必加) |
-a file | 从文件读(不从 stdin) |
-L N | 每 N 行作为一次输入 |
-d <char> | 自定义分隔符 |
-r 防空 stdin (脚本必加)
find /no/such/dir -name "*.log" | xargs rm
# find 返回空、xargs 还是跑了 rm(无参数)→ rm 报错或别的事
GNU xargs 加 --no-run-if-empty 或 -r:
find /no/such/dir -name "*.log" | xargs --no-run-if-empty rm
find /no/such/dir -name "*.log" | xargs -r rm
Linux GNU xargs 默认其实就 -r 行为,macOS BSD xargs 没有 —— 跨平台脚本永远显式加 -r。
-I 和 -n 的区别
# -n 一次喂多个、追加在末尾
echo "1 2 3" | xargs -n 1 echo prefix
# echo prefix 1
# echo prefix 2
# echo prefix 3
# -I 占位符、可放任何位置
echo "1 2 3" | xargs -I {} echo prefix-{}-suffix
# echo prefix-1-suffix
# echo prefix-2-suffix
# echo prefix-3-suffix
-I 强制每行一次(隐式 -n 1),但慢(每次起一个进程)。能用 -n N 时不用 -I。
实战场景
1. 批量 ssh 执行命令
cat hosts.txt | xargs -P 5 -I {} ssh {} 'apt-get update -qq && apt-get upgrade -y'
# 5 节点并行升级
# 失败 host 退出码非 0、想知道是谁失败
cat hosts.txt | xargs -P 5 -I {} sh -c 'ssh {} "uptime" && echo "OK: {}" || echo "FAIL: {}"'
2. 批量删 K8s 资源
# 删所有 Evicted pod
kubectl get pods -A -o json \
| jq -r '.items[] | select(.status.reason=="Evicted") | "\(.metadata.namespace) \(.metadata.name)"' \
| xargs -I {} sh -c 'ns=$(echo {} | awk "{print \$1}"); n=$(echo {} | awk "{print \$2}"); kubectl delete pod -n "$ns" "$n"'
更简洁:
kubectl get pods -A --field-selector=status.phase=Failed -o name \
| xargs -r kubectl delete
3. 删 docker dangling 镜像
docker images -q -f dangling=true | xargs -r docker rmi
# -r 加上,防止没有 dangling 镜像时 docker rmi 报错
4. 找 + 删的标准管道
find /backup -name "*.bak" -mtime +30 -print0 | xargs -0 -r rm
# 老备份清理
5. 并行下载
cat urls.txt | xargs -P 5 -I {} curl -fsSL -O {}
# 5 个并行下载
6. 解决"argument list too long"
rm *.log
# bash: /bin/rm: Argument list too long ← 文件太多
find . -name "*.log" -print0 | xargs -0 rm
# xargs 自动分批喂给 rm
和 -exec 的对比
# find 的 -exec
find . -name "*.log" -exec rm {} +
# xargs
find . -name "*.log" -print0 | xargs -0 rm
| 维度 | -exec | xargs |
|---|---|---|
| 简洁 | 嵌入 find | 管道 |
| 并行 | 不支持 | -P 并行 |
| 性能 | + 模式 OK | 类似 |
| 含特殊字符 | 安全 | 需要 -0 |
简单清理用 -exec +;要并行 / 复杂处理用 xargs。
常见踩坑
坑 1:忘了 -I {} 时位置错
echo "host1 host2" | xargs ssh
# 实际跑:ssh host1 host2
# ssh 把 host2 当成 host1 上要执行的命令 → 行为意外
echo -e "host1\nhost2" | xargs -n 1 ssh
# 或
cat hosts | xargs -I {} ssh {} 'uptime'
坑 2:含空格的文件名被切
ls *.log | xargs rm # ❌ "a b.log" 被切
-print0 + -0 永远配套:
find . -name "*.log" -print0 | xargs -0 rm
或者:
find . -name "*.log" -exec rm {} +
坑 3:-r 不写、空 stdin 还是跑了命令
ls /nonexistent | xargs rm
# rm: missing operand ← GNU 防御了,BSD 没
跨平台必加 -r。
坑 4:管道里管道、变量没传过去
COUNT=0
cat list | xargs -I {} sh -c "COUNT=$((COUNT+1)); echo {} $COUNT"
# COUNT 永远是 0+1=1
每个 xargs spawned subshell 是独立的、$COUNT 不共享。
要计数:
cat list | nl # 用 nl 给行编号
cat list | awk '{print NR, $0}' # awk 用 NR
坑 5:-P 并行让输出乱
cat hosts | xargs -P 10 -I {} ssh {} 'echo $(hostname): up'
# 10 个并行输出交错、看不出谁是谁
每个 ssh 加 prefix / 写到文件:
cat hosts | xargs -P 10 -I {} sh -c 'ssh {} hostname > /tmp/log-{}.log'
或者用专门工具(parallel-ssh / ansible)。
坑 6:xargs -I 不能跨多个变量
# 想给每个 host 加一个端口
cat hosts | xargs -I {} -I {p} echo {}:{p}
# xargs 不支持多占位符
用 awk 或 while 循环:
while read host port; do
echo "$host:$port"
done < hosts-ports.txt
或者 awk:
awk '{print $1":"$2}' hosts-ports.txt
坑 7:echo 转换让 xargs 误判
echo "host1
host2" | xargs ssh
# 不报错但只用 host1
shell 的 echo 在某些情况下吃换行。用 printf:
printf "host1\nhost2\n" | xargs -n 1 ssh
或者读文件:
xargs -a hosts.txt -n 1 ssh
坑 8:xargs 总是非 0 退出码
seq 1 5 | xargs -I {} sh -c 'false'
echo $?
# 123 ← 不是 1
xargs 子命令失败时退出码:
123= 部分子命令失败(不是 0 或 255)124= 子命令被 sig 杀125= xargs 自己出错126= 命令找不到127= 命令不能执行
要让脚本 fail fast:
seq 1 5 | xargs -I {} -P 1 sh -c 'do_stuff {} || exit 255'
# 255 让 xargs 立刻停止
或者别用 xargs、用 for 循环 + set -e。
坑 9:管道前面命令失败不影响 xargs
nonexistent-command | xargs rm
# nonexistent-command not found
# xargs 收到空 stdin → 看坑 3
set pipefail:
set -o pipefail
nonexistent-command | xargs -r rm
# pipefail 让管道任一失败都失败
echo $?
# 127
坑 10:{} 在某些场景被 shell 吃
cat list | xargs -I {} bash -c 'echo {}; ls {}'
# 第一个 {} 被替换、第二个 {} 也被替换(OK)
但是:
cat list | xargs -I {} sh -c "echo {}" # OK
cat list | xargs -I {} echo {} # OK
cat list | xargs -I '{}' echo {} # 也 OK
cat list | xargs echo {} # 错:没 -I 时 {} 字面输出
习惯加引号 xargs -I '{}'。
GNU parallel —— xargs 的强化版
parallel echo ::: a b c
parallel -j 10 ssh {} 'uptime' ::: $(cat hosts)
特点:
- 输出有序
- 错误处理更强
- 进度条
- 复杂占位符(
{1} {2}多列)
但 K8s 节点 / 容器里不一定装。xargs 永远在。