1

我想使用 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 文件。

使用精心设计的解决方案极大地满足一些帮助,该解决方案可以扩展大文件并快速有效地执行操作。

提前致谢。

4

4 回答 4

2

如果输入文件已经是格式良好的 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. 如果 'read' 太慢,你仍然可以使用上面的文本处理方法awk

作为记录,可以按照您的想法构建一个单一的调用 jq 解决方案,如下所示:

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

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

于 2020-11-21T21:41:11.393 回答
1

由于@peak 已经涵盖了问题的 jq 方面,因此我将尝试使用 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
于 2020-11-21T21:47:44.523 回答
1

如果事先不知道输入是有效的 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 
于 2020-11-21T22:01:55.460 回答
1

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

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

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

于 2020-11-21T22:09:27.923 回答