使用并行进程来加速交互式 bash 循环,但我需要在循环内创建一个关联数组

使用并行进程来加速交互式 bash 循环,但我需要在循环内创建一个关联数组

使用 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

这里可能慢的地方是,每个文件运行两个jqs,并分叉一个进程并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]).jsonprintf '%s\0' +([0123456789]).json | xargs -r0 jq ....

虽然您可能也想使用 GNU xargs'-P并行运行其中一些jq,但我建议不要这样做,因为它xargs不能保证命令输出的序列化,因此各个jqs 的输出最终可能会交织在一起。 GNUparallel确实如此,但与解析(可能是)短 JSON 文件这样简单的事情相比,它也有很大的开销,因此可能不会增加太多好处。


1 好吧,jq视为-标准输入,所以严格来说,如果数组中存在这样命名的文件$a(或 glob 的扩展*而不是*.json),您需要将其转换为./-.

相关内容