【问题标题】:Split a JSON array into multiple files using command line tools使用命令行工具将 JSON 数组拆分为多个文件
【发布时间】:2017-03-16 02:42:21
【问题描述】:

假设我们有一个长度为 5 的 JSON 数组,我们想使用 linux 命令行工具将数组拆分为多个长度为 2 的数组,并将分组的项目保存到不同的文件中。

我使用jqsplit 工具进行了尝试(我对任何可以从 bash 脚本执行的方法都很满意):

$ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | jq -c -M '.[]' | split -l 2 -d -a 3 - meta_
$ tail -n +1 meta_*
==> meta_000 <==
{"key1":"value1"}
{"key2":"value2"}

==> meta_001 <==
{"key3":"value3"}
{"key4":"value4"}

==> meta_002 <==
{"key5":"value5"}

前面的命令正确地将项目保存到文件中,但是我们需要将它们转换为有效的 JSON 数组格式。我厌倦了--filter 选项:

$ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | jq -c -M '.[]' | split -l 2 -d -a 3 - meta2_ --filter='jq --slurp -c -M'
[{"key1":"value1"},{"key2":"value2"}]
[{"key3":"value3"},{"key4":"value4"}]
[{"key5":"value5"}]
$ tail -n +1 meta2_*
tail: cannot open 'meta2_*' for reading: No such file or directory

但是,它会在屏幕上显示输出,但不会保留结果。我尝试转发输出,但出现错误:

echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | jq -c -M '.[]' | split -l 2 -d -a 3 - meta2_ --filter='jq --slurp -c -M > $FILE'
...
split: with FILE=meta2_000, exit 2 from command: jq --slurp -c -M > $FILE

任何提示或更好的方法?

编辑:我尝试使用双引号 @andlrc 建议:

$ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | jq -c -M '.[]' | split -l 2 -d -a 3 - meta2_ --filter="jq --slurp -c -M > $FILE"
bash: -c: line 0: syntax error near unexpected token `newline'
bash: -c: line 0: `jq --slurp -c -M > '
split: with FILE=meta2_000, exit 1 from command: jq --slurp -c -M >
$ cat meta_000 | jq --slurp -c -M
[{"key1":"value1"},{"key2":"value2"}]

【问题讨论】:

  • ...您不会接受任何不使用split 的答案? (也就是说:请避免将关于哪些工具是回答问题的最佳方式的偏见放入问题本身)。
  • @CharlesDuffy 我可以在不使用拆分的情况下接受和回答,感谢您的建议

标签: json bash split jq


【解决方案1】:

假设我们有一个长度为 5 的 JSON 数组,我们想要拆分 数组到长度为 2 的多个数组中,并将分组的项目保存到 不同的文件,使用 linux 命令行工具

Xidel,一个 HTML/XML/JSON 解析器(使用 CSS、XPath、XQuery、JSONiq 和模式模板),可以做你想做的事。
提取查询本身:

$ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | \
xidel -s - --xquery '
  for $x in 1 to count($json())
  where $x mod 2 = 1
  return
  [
    $json($x),
    $json($x+1)
  ]
' --printed-json-format=compact
[{"key1": "value1"}, {"key2": "value2"}]
[{"key3": "value3"}, {"key4": "value4"}]
[{"key5": "value5"}]

将每一行保存为 json 文件(serialize-json() 在这里很重要):

`$ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' | \
xidel -s - --xquery '
  for $x at $i in 1 to count($json())
  where $x mod 2 = 1
  count $i
  return
  file:write(
    concat(
      "output_",
      $i,
      ".json"
    ),
    serialize-json(
      [
        $json($x),
        $json($x+1)
      ]
    )
  )
'

这会导致:

$ cat output_1.json
[{"key1": "value1"}, {"key2": "value2"}]

$ cat output_2.json
[{"key3": "value3"}, {"key4": "value4"}]

$ cat output_3.json
[{"key5": "value5"}]

【讨论】:

    【解决方案2】:

    jq 可能是其他回复中提到的方式。由于我对 jq 不熟悉,所以我在下面使用非常常见的命令(echo、cat、wc、head、tail、sed、expr)编写了一个 bash 脚本(splitjson.sh)。此脚本将 json 文件拆分为不超过指定字节数的块。如果在指定的字节数内无法拆分(json 项很长或指定的每个块的最大字节数太小),脚本将停止写入 json 文件并写入错误。

    这是一个示例,问题中的数据为 example.json:

    [{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]
    

    以每个块的最大字节数执行脚本的命令是:

    $ ./splitjson.sh example.json 40
    

    那么结果是:

    $ head example.json.*
    ==> example.json.0 <==
    [{"key1":"value1"},{"key2":"value2"}]
    ==> example.json.1 <==
    [{"key3":"value3"},{"key4":"value4"}]
    ==> example.json.2 <==
    [{"key5":"value5"}]
    

    脚本处理结束括号“}”、冒号“,”和开始括号“{”之间的空格、制表符、换行符的情况。

    我在 82 MB 的 json 文件上成功使用了这个脚本。我希望它可以处理更大的文件。

    这里是脚本(splitjson.sh):

    #!/bin/bash
    if [ $# -ne 2 ]
    then
        echo "usage: $0 file_to_split.json nb_bytes_max_per_split"
        exit 1
    fi
    if [[ -r $1 ]]
    then
        input=$1
        echo "reading from file '$input'"
    else
        echo "cannot read from specified input file '$1'"
        exit 2
    fi
    if [[ $2 = *[[:digit:]]* ]]; then
        maxbytes=$2
        echo "taking maximum bytes '$maxbytes'"
    else
        echo "provided maximum number of bytes '$2' is not numeric"
        exit 3
    fi
    
    start=0
    over=0
    iteration=0
    inputsize=`cat $input|wc -c`
    tailwindow="$input.tail"
    echo "input file size: $inputsize"
    tmp="$input.tmp"
    cp $input $tmp
    sed -e ':a' -e 'N' -e '$!ba' -e 's/}[[:space:]]*,[[:space:]]*{/},{/g' -i'.back' $tmp
    rm "$tmp.back"
    inputsize=`cat $tmp|wc -c`
    if [ $inputsize -eq 0 ]; then
        cp $input $tmp
        sed -e 's/}[[:space:]]*,[[:space:]]*{/},{/g' -i'.back' $tmp
        rm "$tmp.back"
    fi
    inputsize=`cat $tmp|wc -c`
    while [ $over -eq 0 ]; do
        output="$input.$iteration"
        if [ $iteration -ne 0 ]; then
                    echo -n "[{">$output
        else
                    echo -n "">$output
        fi
        tailwindowsize=`expr $inputsize - $start`
        cat $tmp|tail -c $tailwindowsize>$tailwindow
        tailwindowresultsize=`cat $tailwindow|wc -c`
        if [ $tailwindowresultsize -le $maxbytes ]; then
            cat $tailwindow>>$output
            over=1
        else
            cat $tailwindow|head -c $maxbytes|sed -E 's/(.*)\},\{(.*)/\1}]/'>>$output
        fi
        jsize=`cat $output|wc -c`
        start=`expr $start + $jsize`
        if [ $iteration -eq 0 ]; then
            start=`expr $start + 1`
        else
            start=`expr $start - 1`
        fi
        endofj=`cat $output|tail -c 3`
        if [ $over -ne 1 ]; then
            if [ ${endofj:1:2} != "}]" ]; then
                if [ ${endofj:0:2} != "}]" ]; then
                    echo -e "ERROR: at least one split pattern wasn't found. Aborting. This could be due to wrongly formatted json or due to a json entry too long compared to the provided maximum bytes. Maybe you should try increasing this parameter?\a"
                    exit 4
                fi
            fi
        fi
        jsizefinal=`cat $output|wc -c`
        echo "wrote $jsizefinal bytes of json for iteration $iteration to $output"
        iteration=`expr $iteration + 1`
    done
    rm $tailwindow
    rm $tmp
    

    【讨论】:

      【解决方案3】:

      我使用jqsplit 工具找到了解决方案。我错过了双引号,jq 中的'.' 模式,并用反斜杠转义了$

      $ echo '[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]' |
        jq -c -M '.[]' |
        split -l 2 -d -a 3 - meta2_ --filter="jq --slurp -c -M '.' >\$FILE"
      $ tail -n +1 meta2_*
      ==> meta2_000 <==
      [{"key1":"value1"},{"key2":"value2"}]
      
      ==> meta2_001 <==
      [{"key3":"value3"},{"key4":"value4"}]
      
      ==> meta2_002 <==
      [{"key5":"value5"}]
      

      【讨论】:

      • 喜欢这个,因为它不需要额外的循环
      【解决方案4】:

      在 jq 过滤器中构建数组会更容易,然后按行拆分为文件。无需额外过滤。

      range(0; length; 2) as $i | .[$i:$i+2]
      

      产生:

      [{"key1":"value1"},{"key2":"value2"}]
      [{"key3":"value3"},{"key4":"value4"}]
      [{"key5":"value5"}]
      

      所以把它们放在一起。

      $ jq -cM --argjson sublen '2' 'range(0; length; $sublen) as $i | .[$i:$i+$sublen]' \
          input.json | split -l 1 -da 3 - meta2_
      

      【讨论】:

        【解决方案5】:

        拆分成两个单独的jq 调用允许第二个调用使用input 帮助器一次只处理一个输入。在第二个中使用 try 帮助器可以优雅地处理不完整的行,如果您没有留下两项输入。


        s='[{"key1":"value1"},{"key2":"value2"},{"key3":"value3"},{"key4":"value4"},{"key5":"value5"}]'
        
        jq '.[]' <<<"$s" | \
          jq -c -n 'repeat(input as $i1 | try (input as $i2 | [$i1, $i2]) catch [$i1])?' | \
          split -l 2 -d -a 3 - meta_
        

        ...发射,在第一个文件中:

        [{"key1":"value1"},{"key2":"value2"}]
        [{"key3":"value3"},{"key4":"value4"}]
        

        ...并且,在第二个中:

        [{"key5":"value5"}]
        

        【讨论】:

          猜你喜欢
          • 2021-03-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-21
          • 2013-11-14
          • 2014-10-04
          • 1970-01-01
          • 2019-01-06
          相关资源
          最近更新 更多