【问题标题】:BASH How to improve performance of JSON parsing with jqBASH 如何使用 jq 提高 JSON 解析的性能
【发布时间】:2022-01-06 09:04:11
【问题描述】:

上下文:我有一个场景,我需要将备份从一个系统复制到另一个系统。我希望备份列表是可配置的,所以我在脚本本身中使用了 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 很大时,这似乎需要一些时间(我已经为这篇文章减少了 3 个)。看起来我解析 JSON 的方式非常复杂,但我无法让它以任何其他方式工作。

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

【问题讨论】:

  • 我会使用 csv 文件和 GNU Parallel parallel --csv 没有 jq 并且由于并行执行而更快。我相信你也可以将 csv 转换为 python dict。
  • 我会切换到具有 JSON 解析器的语言,而不是使用 jq 来尝试生成 bash 可以处理的更简单的输入格式。
  • @chepner 如果只是提供一个可以用 bash 解析的输出,那么 jq 就足够了,不是吗?
  • “字典”就是某些语言所称的关联数组。在 JSON 中,它们被称为“对象”。
  • @Fravadona 仅假设您对存储在对象中的值有足够的了解,以了解构造 CSV 或 TSV 值是否安全。问题是没有适当的数据结构可以将值从jq 传递到bash,只有字符串。使用不需要依赖此类字符串编码的语言将值从一个函数传递到另一个更简单。

标签: arrays json bash performance jq


【解决方案1】:

更新

需要考虑的几点:

  • 您可以避免在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 $0}'
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

【讨论】:

  • 这真的很有见地。特别是关于简化循环的第一部分。现在看起来很明显,但我没有能力改进它。我认为关键用户或路径中不会有逗号,但是 TSV 方法看起来更干净,更容易被其他人阅读,同时使其更健壮。非常感谢您。
  • 你能解释一下select(.key and .user and .path)背后的原因吗?我试过你以前版本的答案,它只是&lt;(jq -r '.[] | [.key,.user,.path]|@tsv ' &lt;&lt;&lt;"$backups_to_perform"),它工作正常。
  • @pr3c0g 以前的版本测试了path 变量以确定是否存在空字段(并为其打印警告)。现在用这个jq的select直接丢弃包含空属性的JSON对象;如果您在# safety check 期间退出脚本,那么您将不需要它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-22
  • 1970-01-01
  • 2015-01-06
相关资源
最近更新 更多