使用 Bash,我有一个代表文件列表的索引数组:
a=("1.json" "2.json" "3.json" ... "5309.json")
我将这些 JSON 文件中的两个数据字段解析为两个关联数组:
declare -A idArr
declare -A valueArr
for i in "${a[@]}"; do
jqId="$(jq -M ".fileId" <"${i}")"
jqValue="$(jq -M ".value" <"${i}")"
# If there are already items in the associative array, add the new items separated by a newline
idArr[${i}]="${idArr[${i}]}${idArr[${i}]:+$'\n'}${jqId}"
valueArr[${jqId}]="${valueArr[${jqId}]}${valueArr[${jqId}]:+$'\n'}${jqValue}"
done
由于我一次迭代一个文件,因此需要相当长的时间来处理所有文件。我要求在循环中创建的关联数组持续超出其范围,即使在循环完成后也是如此。
是否有一种方法(例如使用parallel
处理或任何其他方法)允许我同时处理多个数组项并仍然使它们能够向关联数组提供数据?
答案1
这里可能慢的地方是,每个文件运行两个jq
s,并分叉一个进程并jq
在其中执行,这可能比处理一个小 json 文件要多几个数量级的工作。
read0() { IFS= read -rd '' "$@"; }
add_line() {
typeset -n _var="$1"
_var+=${_var:+$'\n'}$2
}
shopt -s extglob failglob lastpipe
set -o pipefail
typeset -A idArr valueArr
jq -j '[input_filename, .fileId, .value] |
map(gsub("\u0000"; "") + "\u0000") | add
' -- +([0123456789]).json |
while read0 file && read0 id && read0 value; do
add_line "idArr[$file]" "$id"
add_line "valueArr[$file]" "$var"
done
只会运行jq
一次。jq
打印文件名、id 和值(如果有的话,去掉它们的 NUL),以 NUL 分隔,bash 在循环中并行读取。
重要的提示: 做不是使用任意文件名调用它add_line
,因为这将是一个命令注入漏洞,因为typeset -n
它有点eval
伪装。例如,如果您使用*.json
代替+([0123456789]).json
,并且有一个名为 的文件$(reboot).json
,则会重新启动!
在当前版本的 bash 中,可以通过在 , 周围使用单引号而不是双引号来解决这个问题idArr[$file]
,valueArr[$file]
但这可能不是面向未来的,因为未来版本的 bash 可能会决定不再对 nameref 取消引用进行这些扩展以避免这种漏洞。
或者,您可以通过消除那些设计错误的名称引用并eval
显式使用来消除这些漏洞:
add_line() {
eval "$1+=\${$1:+\$'\\n'}\$2"
}
并确保将其称为:
add_line 'idArr[$file]' "$id"
add_line 'valueArr[$file]' "$var"
然后您就可以使用*.json
或"${a[@]}"
不需要进行一些消毒。
如果遇到“参数列表太长”错误,请替换jq ... +([0123456789]).json
为printf '%s\0' +([0123456789]).json | xargs -r0 jq ...
.
虽然您可能也想使用 GNU xargs
'-P
并行运行其中一些jq
,但我建议不要这样做,因为它xargs
不能保证命令输出的序列化,因此各个jq
s 的输出最终可能会交织在一起。 GNUparallel
确实如此,但与解析(可能是)短 JSON 文件这样简单的事情相比,它也有很大的开销,因此可能不会增加太多好处。
1 好吧,jq
视为-
标准输入,所以严格来说,如果数组中存在这样命名的文件$a
(或 glob 的扩展*
而不是*.json
),您需要将其转换为./-
.