Bash 中的 `command < <(command)` 是什么意思?
command1 < <(command2) 把 command2 的标准输出接到 command1 的标准输入。
下面这条命令会把 yq 的输出按行读入 Bash 数组 actions:
readarray actions < <(yq '.coolActions[]' sample.yaml)
这里组合了两种语法:单独的 < 是输入重定向,<(...) 是进程替换(process substitution)。
<:指定标准输入的来源
从文件读取数据时,可以这样写:
readarray actions < actions.txt
< actions.txt 让 readarray 从文件读取,不再等待终端输入。这里重定向的是标准输入,也就是文件描述符 0。
原命令中的第一个 < 起同样的作用,只是右侧换成了进程替换:
readarray actions < <(...)
<(...):提供读取命令输出的路径
Bash 遇到下面的表达式时,会异步运行括号内的命令,并提供一个可读取其标准输出的文件名:
<(yq '.coolActions[]' sample.yaml)
在使用 /dev/fd 的系统上,这个文件名可能形如 /dev/fd/63,具体编号不固定;其他环境可能使用命名管道。
整条命令可以理解为:
readarray actions < 某个连接到yq输出的路径
Bash 无须先把全部输出保存成普通磁盘文件。yq 产生数据时,readarray 就可以读取。
写法上有两点要注意:
<(必须连在一起,不能写成< (。- 外层的
<与<(...)要分开;写成<<会涉及 here-document 的语法。
示例命令如何得到数组
假设所用的 yq 支持这个表达式,并逐行输出以下字符串:
create
edit
delete
整条命令的数据流是:
sample.yaml → yq 的标准输出 → readarray 的标准输入 → actions 数组
.coolActions[] 取出 coolActions 序列中的各个元素。readarray 是 Bash 内建命令,也叫 mapfile,默认将每一行保存为一个数组元素,下标从 0 开始。
readarray 默认会保留每行末尾的换行符。假设上述三行都以换行符结尾,数组内容相当于:
actions=($'create\n' $'edit\n' $'delete\n')
如果需要去掉行尾的换行符,可以加上 -t:
readarray -t actions < <(yq '.coolActions[]' sample.yaml)
此时数组内容相当于:
actions=('create' 'edit' 'delete')
读取和遍历数组:
printf '%s\n' "${actions[0]}"
# create
for action in "${actions[@]}"; do
printf 'action: %s\n' "$action"
done
循环输出为:
action: create
action: edit
action: delete
"${actions[@]}" 外面的双引号要保留,这样每个数组元素中的空格和通配符字符才能原样传递。
为什么不用管道
下面的写法也会把 yq 的输出传给 readarray:
yq '.coolActions[]' sample.yaml | readarray -t actions
但在 Bash 的默认行为下,管道里的 readarray 会在子 shell 中执行。数组赋值发生在子 shell 中,管道结束后,当前 shell 中的 actions 不会得到这次赋值。启用 lastpipe 且满足其条件时,行为可能不同。
使用进程替换时:
readarray -t actions < <(yq '.coolActions[]' sample.yaml)
readarray 在当前 shell 中执行,后续命令可以直接访问 actions。
使用时还要注意什么
- 需要使用提供
readarray的 Bash 环境。readarray和<(...)都不属于 POSIXsh的通用语法,不应直接用sh script.sh执行这类脚本。 - 名为
yq的工具有不同实现,表达式和输出选项可能不同。示例以它能输出上述三行为前提;数组实际接收到的内容取决于yq的标准输出。 readarray -t按行拆分数据。如果某个 YAML 字符串本身包含换行符,就不能再假定“一个 YAML 元素对应一个数组元素”。- 这条命令结束后的
$?是readarray的退出状态,不能直接用它判断进程替换中的yq是否成功。
备注:内容仅供参考。
