如何使用linux shell脚本将行分隔的JSON拆分为多个文件

如何使用linux shell脚本将行分隔的JSON拆分为多个文件,json,linux,shell,unix,jq,Json,Linux,Shell,Unix,Jq,我有一个巨大的以换行符分隔的JSON文件input.JSON,如下所示: { "name":"a.txt", "content":"...", "other_keys":"..."} { "name":"b.txt", "content":"...", "something_else":"..."} { "name":"c.txt", "content":"...", "etc":"..."} ... 如何将其拆分为多个文本文件,其中文件名取自“名称”,文件内容取自“内容”?其他键可以忽略。目

我有一个巨大的以换行符分隔的JSON文件input.JSON,如下所示:

{ "name":"a.txt", "content":"...", "other_keys":"..."}
{ "name":"b.txt", "content":"...", "something_else":"..."}
{ "name":"c.txt", "content":"...", "etc":"..."}
...

如何将其拆分为多个文本文件,其中文件名取自“名称”,文件内容取自“内容”?其他键可以忽略。目前没有运气玩弄
jq
工具。

jq
没有输出功能,无法在分组对象后创建所需的文件;您需要在JSON库中使用另一种语言。使用Python的示例如下:

import json
import fileinput

for line in fileinput.input():  # Read from standard input or filename arguments
    d = json.loads(line)
    with open(d['name'], "a") as f:
        print(d['content'], file=f)
这样做的缺点是多次重复打开和关闭每个文件,但很简单。一个更复杂但更高效的示例将使用出口堆栈上下文管理器

import json
import fileinput
import contextlib

with contextlib.ExitStack() as es:
    files = {}
    for line in fileinput.input():
        d = json.loads(line)
        file_name = d['name']
        if file_name not in files:
            files[file_name] = es.enter_context(open(file_name, "w"))
        print(d['content'], file=files[file_name])
简而言之,文件在被发现时被打开并缓存。循环完成后(或在发生异常时),退出堆栈确保之前打开的所有文件都正确关闭


如果有可能会有太多的文件同时打开,您将不得不使用简单但效率低下的代码,尽管您可以实现更复杂的功能,只需在任何给定时间保持少量固定数量的文件处于打开状态,并在必要时以追加模式重新打开它们。但是,实现这一点超出了本答案的范围。

高效、基于jq的解决方案的关键是将jq的输出(使用-c选项调用)通过管道传输到诸如awk之类的程序,以执行输出文件的实际写入

jq -c '.name, .content' input.json | 
  awk 'fn {print > fn; close(fn); fn=""; next;}
       {fn=$0; sub(/^"/,"",fn); sub(/"$/,"",fn);}' 
警告 盲目依赖JSON输入文件名会带来一些风险, e、 g

  • 如果多次指定相同的“名称”,该怎么办
  • 如果一个文件已经存在,上面的程序将简单地附加到它
此外,应该检查.name作为文件名的有效性

有关答案 这个问题以前是以稍微不同的形式提出和回答的,
例如,请参见

以下基于jq的解决方案可确保JSON文件中的输出得到很好的打印, 但忽略任何.content等于JSON字符串的输入对象:“忽略我”:


jq
可以收集具有相同名称和内容的对象,但不能打开和写入任意文件。
jq 'if .content == "IGNORE ME" 
    then "Skipping IGNORE ME" | stderr | empty
    else .name, .content, "IGNORE ME" end' input.json |
    awk '/^"IGNORE ME"$/ {close(fn); fn=""; next}
         fn {print >> fn; next}
         {fn=$0; sub(/^"/,"",fn); sub(/"$/,"",fn);}'