jq:在从 json 文件和 bash stdout 读取输入时插入新对象

jq: insert new objects while reading inputs from json file and bash stdout

我想使用 bash 生成的 uuid 在 json 个对象之间插入新的 json 个对象。

输入json文件test.json

{"name":"a","type":1}
{"name":"b","type":2}
{"name":"c","type":3}

输入bash命令uuidgen -r

目标输出json

{"id": "7e3ca7b0-48f1-41fe-9a19-092a62cba0dc"}
{"name":"a","type":1}
{"id": "3f793fdd-ec3b-4306-8153-12f3f9faf2c1"}
{"name":"b","type":2}
{"id": "cbcd759a-37e7-4da7-b7fe-7572f474ec31"}
{"name":"c","type":3}

用于插入新对象的基本 jq 程序

jq -c '{"id"}, .' test.json

输出json

{"id":null}
{"name":"a","type":1}
{"id":null}
{"name":"b","type":2}
{"id":null}
{"name":"c","type":3}

jq 程序插入从 bash:

生成的 uuid
jq -c '{"id" | input}, .' test.json < <(uuidgen)

不确定如何处理两个输入,bash命令用于在新对象中创建值,以及要转换的输入文件(新对象插入每个对象之间)。

我想处理大小不超过 json 的文件,每个文件最多几千兆字节。

非常希望得到一些设计良好的解决方案的帮助,这些解决方案可以针对大型文件扩展并快速高效地执行操作。

提前致谢。

如果输入文件已经是格式正确的 JSONL,那么一个简单的 bash 解决方案是:

while IFS= read -r line; do
  printf "{\"id\": \"%s\"}\n" $(uuidgen)
  printf '%s\n' "$line"
done < test.json

如果 test.json 非常大并且已知是有效的 JSONL,这可能是最好的简单解决方案。

如果输入文件不是 JSONL,那么您仍然可以通过管道输入 jq -c . test.json 使用上述方法。如果“读取”太慢,您仍然可以使用上述文本处理方法 awk

郑重声明,按照您所想的方式,可以按如下方式构造一个 jq 单调用解决方案:

jq -n -c -R --slurpfile objects test.json '
  $objects[] | {"id": input}, .' <(while true ; do uuidgen ; done)

显然你不能“吞噬”无限的 uuidgen 值流;也许不太明显,如果您只是简单地在流中进行管道传输,该过程将挂起。

以来,我将尝试使用 Python 更有效地执行此操作,仍然包装以便可以在 shell 脚本中调用它。

这假设您的输入是 JSONL,每行一个文档。如果不是,请考虑先通过 jq -c . 管道,然后再管道进入下方。

#!/usr/bin/env bash

py_prog=$(cat <<'EOF'
import json, sys, uuid

for line in sys.stdin:
    print(json.dumps({"id": str(uuid.uuid4())}))
    sys.stdout.write(line)
EOF
)

python -c "$py_prog" <in.json >out.json

如果事先不知道输入是有效的 JSONL, 以下 bash+jq 解决方案之一可能有意义 因为计算对象数量的开销相对较小。

如果输入足够小以适合内存,您可以采用简单的解决方案:

n=$(jq -n 'reduce inputs as $in (0; .+1)' test.json)

for ((i=0; i < $n; i++)); do uuidgen ; done |
jq -n -c -R --slurpfile objects test.json '
  $objects[] | {"id": input}, .'

否则,也就是说,如果输入非常大,那么可以避免像这样吞掉它:

n=$(jq -n 'reduce inputs as $in (0; .+1)' test.json)
jq -nc --rawfile ids <(for ((i=0; i < $n; i++)); do uuidgen ; done) '
  $ids | split("\n") as $ids
  | foreach inputs as $in (-1; .+1; {id: $ids[.]}, $in)
' test.json 

这是另一种方法,其中 jq 将输入作为原始字符串处理,已经由 bash 的单独副本混合。

while IFS= read -r line; do
  uuidgen
  printf '%s\n' "$line"
done | jq -Rrc '({ "id": . }, input)'

它仍然具有每个输入行调用一次 uuidgen 的所有性能开销(加上一些额外的开销,因为 bash 的 read 一次操作一个字节)--但它在固定数量的内存中运行而不需要 Python.