xargs 结合 bash -c 参数传递映射规则笔记
背景
命令printf '3008\n3009\n' | xargs -n1 bash -lc 'echo "0=$0 1=$1"' _
中带有"_"符号,引发的思考与深入。
1. 角色分工:xargs vs bash -c
1.1 xargs 做什么?
xargs 的职责非常简单:
从 stdin 读入内容 → 按分隔规则拆成参数 → 追加到你写的命令后面 → 执行。
它不理解 $0/$1,也不理解 _,只负责"拼参数"。
1.2 bash -c 做什么?
bash -c(同理 sh -c)有一条固定规则:
text
bash -c 'commands' arg0 arg1 arg2 ...
($0) ($1) ($2)
commands:要执行的命令字符串commands后面的第一个额外参数 → 变成脚本里的$0- 后面依次 →
$1,$2, ...
所以 $0 这件事,是 bash/sh 的规则。
2. 为什么要加 _:不加会怎样?
为了看清楚本质,先不用 xargs,直接用 bash 演示。
2.1 不加占位符:第一个数据会被当成 $0
bash
bash -lc 'echo "0=$0 1=$1 2=$2"' 3008
你会看到类似:
0=30081=2=
原因:3008 被当作 arg0,也就是 $0 了。
2.2 加一个占位符:让真实数据从 $1 开始
bash
bash -lc 'echo "0=$0 1=$1 2=$2"' _ 3008
你会看到:
0=_1=30082=
_完全不特殊,你写dummy、x、scriptname都可以:
bash -lc '...' dummy 3008效果一样。教程常用
_只是因为它短、习惯性当占位。
3. 把它放回 xargs:你写的命令会被拼成什么?
3.1 用 xargs -t 打印"真实执行的命令"(强烈推荐)
-t 会打印 xargs 最终执行的完整命令行,直接破除一切误解。
bash
printf '3008\n' | xargs -n1 -t bash -lc 'echo "0=$0 1=$1"' _
你会看到类似输出(不同系统略有差异):
bash -lc 'echo "0=$0 1=$1"' _ 3008
0=_ 1=3008
这就证明:
_是你写在命令里的固定参数3008是 xargs 从 stdin 读出来追加进去的参数
4. 教学案例库
案例 1:单列输入(每行一个 IID)
bash
printf '%s\n' 3008 3009 3010 |
xargs -n1 bash -lc 'iid="$1"; echo "IID=$iid"' _
说明:
-n1每次取 1 个参数iid="$1"才能拿到 IID(因为$0被_占了)
案例 2:两列输入(iid + 输出文件),映射 $1/$2
假设输入是:
3008 ./docs/3008.json
3009 ./docs/3009.json
命令:
bash
printf '%s\n' \
"3008 ./docs/3008.json" \
"3009 ./docs/3009.json" |
xargs -n2 bash -lc '
iid="$1"
out="$2"
echo "iid=$iid out=$out"
' _
要点:
-n2表示每次拿 2 个参数($1和$2)
案例 3:三列输入(iid + dir + filename),映射 $1/$2/$3
bash
printf '%s\n' \
"3008 ./docs 3008.json" \
"3009 ./docs 3009.json" |
xargs -n3 bash -lc '
iid="$1"
dir="$2"
file="$3"
mkdir -p -- "$dir"
echo "save $iid -> $dir/$file"
' _
案例 4:并发执行(下载类任务常用)
bash
printf '%s\n' 3008 3009 3010 3011 |
xargs -n1 -P4 bash -lc '
iid="$1"
echo "pid=$$ iid=$iid"
# curl ... "$iid" ...
' _
要点:
-P4并发 4 个进程- 每个子 bash 进程里
$$不同
案例 5:安全处理"带空格的路径/文件名"(必须用 NUL 分隔)
如果你的 dir/file 可能包含空格、换行、奇怪字符,普通空白分隔会炸。标准做法是:
- 上游输出用
\0分隔字段 - xargs 用
-0
示例(构造 NUL 数据流):
bash
python3 - <<'PY'
items = [
("3008", "./docs with space", "a b.json"),
("3009", "./docs", "c.json"),
]
import sys
for iid, d, f in items:
sys.stdout.write(iid + "\0" + d + "\0" + f + "\0")
PY
| xargs -0 -n3 bash -lc '
iid="$1"; dir="$2"; file="$3"
mkdir -p -- "$dir"
echo "iid=$iid dir=[$dir] file=[$file]"
' _
6. -l 的作用是?如命令 bash -lc
-c:执行命令字符串,并启用 arg0→0、arg1→1... 的参数映射规则-l:以 login shell 启动,会读取/etc/profile、~/.bash_profile等,影响 PATH/环境变量等_占$0的规则 只跟-c相关 ,跟-l无关
所以:
bash -c '...' _ 3008bash -lc '...' _ 3008(多了 login 环境加载)
6. 速记模板
单参数模板
bash
... | xargs -n1 bash -lc 'var="$1"; ...' _
多参数模板(每次 N 个字段)
bash
... | xargs -nN bash -lc 'a="$1"; b="$2"; c="$3"; ...' _
并发模板
bash
... | xargs -n1 -P4 bash -lc 'iid="$1"; ...' _
复杂文件名模板(NUL)
bash
...(输出以 \0 分隔)... | xargs -0 -nN bash -lc '...' _