jq:在读取json文件和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}

插入bash:生成的uuid的jq程序

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

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

我想处理大小json文件,每个文件最多几GB。

设计良好的解决方案能够扩展到大文件并快速高效地执行操作,非常值得帮助。

提前谢谢。

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

while IFS= read -r line; do
printf "{"id": "%s"}n" $(uuidgen)
printf '%sn' "$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)

显然你不能"诋毁";乌伊根值的无界流;也许不太明显,如果你只是在流中管道,这个过程就会挂起。

由于@peak已经解决了问题的jq方面,我将尝试使用Python更有效地解决这一问题,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

这里有另一种方法,jq将输入作为原始字符串处理,该字符串已经由bash的单独副本进行了复用。

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

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

如果事先不知道输入是有效的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 

最新更新