BASH 如何提高 JSON 使用 jq 解析的性能

BASH How to improve performance of JSON parsing with jq

上下文:我有一个场景,我需要执行从一个系统到另一个系统的备份副本。我希望备份列表是可配置的,所以我在脚本本身内部采用了 JSON 方法。

该列表包含密钥(输出中显示的备份名称)、ssh 用户以及从中获取备份的路径。

示例:

backups_to_perform='[
  {
    "key": "key1",
    "user": "user1",
    "path": "path1"
  },
  {
    "key": "key2",
    "user": "user2",
    "path": "path2"
  },
  {
    "key": "key3",
    "user": "user3",
    "path": "path3"
  },
]'

我选择 JSON 的原因是我希望具有与 python 字典类似的结构,因为关联数组只能有 key:pair,而不是key{key:pair; key:pair}(如有错误请指正)

这就是我解析 JSON:

的方式
  while read -r backup; do
    IFS=, read -r key user path <<<"$(jq -cr '"\(.key),\(.user),\(.path)"' <<<"$backup")"
    rsync_backup "$key" "$user" "$path"
  done < <(jq -cr '.[]' <<<"$backups_to_perform")

rsync_backup 只是一个执行接受这些参数的 rsync 的函数。

可能有更好的解决方案来实现我想要的备份副本,但我想改进此类代码以便下次更好地应用它。

我的问题是,当 JSON 很大时,这似乎需要一些时间(我已将此 post 缩减为 3)。看起来我解析 JSON 的方式非常复杂,但我无法以任何其他方式让它工作。

我调用 jq 一次来提供循环,然后在每次迭代中再次调用它可能很糟糕。

更新

需要考虑的几点:

  • 您可以避免在 while 循环中使用 jq
#!/bin/bash

while IFS=',' read -r key user path
do
#   rsync_backup "$key" "$user" "$path"
    echo "key=$key user=$user path=$path"
done < <(
    jq -cr '.[] | "\(.key),\(.user),\(.path)"' <<< "$backups_to_perform"
)
  • 您应该防止 JSON 中的拼写错误导致 null 值(例如,如果您输入 "usr": 而不是 "user": ).

  • 您应该允许在 "key":"user": 中使用逗号,在 "path": 中允许使用任何字符(但 NULL BYTE 除外)。


考虑到所有这些,我会选择 TSV 格式作为 jq 输出:

#!/bin/bash

# safety check
if $(jq 'any(.[]; .key and .user and .path | not)' <<< "$backups_to_perform")
then
    jq -c '.[] |select(.key and .user and .path |not)' <<< "$backups_to_perform" |
    awk -v prefix="[WARNING] missing attribute in record: " '{print prefix [=11=]}'
fi

# doing the backups
while IFS=$'\t' read -r key user path
do
    # unescape TSV values
    printf -v key  %b "$key"
    printf -v user %b "$user"
    printf -v path %b "$path"
#   rsync_backup "$key" "$user" "$path"
    echo "key=$key user=$user path=$path"
done < <(
    jq -r '.[] | select(.key and .user and .path) | [.key,.user,.path] | @tsv' <<< "$backups_to_perform"
)

你可以用这个输入来测试它:

IFS='' read -r -d '' backups_to_perform <<'EOJ'
[
  {
    "__comment__": "comma in key value",
    "key": "key,1",
    "user": "user1",
    "path": "path1"
  },
  {
    "__comment__": "newline in key value",
    "key": "key\n2",
    "user": "user2",
    "path": "path2"
  },
  {
    "__comment__": "mispelled user attribute",
    "key": "key3",
    "usr": "user3",
    "path": "path3"
  },
  {
    "__comment__": "path containing ascii range [0x01-0x7f]",
    "key": "key4",
    "user": "user4",
    "path": "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f"
  }
]
EOJ