Bash / * NIX:将文件拆分为子字符串上的多个文件

时间:2017-02-24 17:31:24

标签: bash macos shell geojson

此问题的变体之前已被问及并得到解答,但我发现我的sed / grep / awk技能从那些工作到自定义解决方案太过简陋,因为我几乎没有在shell脚本中工作。

我有一个相当大的(100K +行)文本文件,其中每行定义一个GeoJSON对象,每个这样的对象包括一个名为" county" (总而言之,有100个不同的县)。这是一个片段:

{"type": "Feature", "properties": {"county":"ALAMANCE", "vBLA": 0, "vWHI": 4, "vDEM": 0, "vREP": 2, "vUNA": 2, "vTOT": 4}, "geometry": {"type":"Polygon","coordinates":[[[-79.537429,35.843303],[-79.542428,35.843303],[-79.542428,35.848302],[-79.537429,35.848302],[-79.537429,35.843303]]]}},
{"type": "Feature", "properties": {"county":"NEW HANOVER", "vBLA": 0, "vWHI": 0, "vDEM": 0, "vREP": 0, "vUNA": 0, "vTOT": 0}, "geometry": {"type":"Polygon","coordinates":[[[-79.532429,35.843303],[-79.537428,35.843303],[-79.537428,35.848302],[-79.532429,35.848302],[-79.532429,35.843303]]]}},
{"type": "Feature", "properties": {"county":"ALAMANCE", "vBLA": 0, "vWHI": 0, "vDEM": 0, "vREP": 0, "vUNA": 0, "vTOT": 0}, "geometry": {"type":"Polygon","coordinates":[[[-79.527429,35.843303],[-79.532428,35.843303],[-79.532428,35.848302],[-79.527429,35.848302],[-79.527429,35.843303]]]}},

我需要将其拆分为100个单独的文件,每个文件包含一个县的GeoJSON,每个文件名为xxxx_bins_2016.json(其中xxxx是县名)。我也想在每个这样的文件末尾的最后一个字符(逗号)消失。

我在Mac OSX中这样做,如果这很重要的话。我希望通过研究你可以建议的任何解决方案来学到很多东西,所以如果你想花时间解释为什么'以及'什么'这太棒了。谢谢!

已编辑,以明确有不同的县名,其中一些是双字名。

5 个答案:

答案 0 :(得分:6)

jq kind 可以做到这一点;它可以对输入进行分组,并为每组输出一行文本。然后shell负责将每一行写入适当命名的文件。 jq本身并不具备打开文件进行书写的能力,这样您就可以在一个过程中完成这项工作。

jq -Rn -c '[inputs[:-1]|fromjson] | group_by(.properties.county)[]' tmp.json |
  while IFS= read -r line; do
    county=$(jq -r '.[0].properties.county' <<< $line)
    jq -r '.[]' <<< "$line" > "$county.txt"
done

[inputs[:-1]|fromjson]以字符串形式读取文件的每一行,删除尾随的逗号,然后将该行解析为JSON并将这些行包装成单个数组。生成的数组按县名排序和分组,然后写入标准输出,每行一组。

shell循环读取每一行,通过调用jq从组的第一个元素中提取县名,然后再次使用jq将组的每个元素写入相应的文件,每行一个元素。

(快速查看https://github.com/stedolan/jq/issues似乎无法显示output函数的任何请求,该请求可让您在jq内打开并写入文件过滤器。我想的是像

这样的东西
jq -Rn '... | group_by(.properties.county) | output("\(.properties.county).txt")' tmp.json

不需要shell循环。)

答案 1 :(得分:5)

如果使用字符串解析而不是正确的 JSON解析来提取县名是可以接受的 - 一般来说很脆弱,但可以在这个简单的工作案例 - 考虑Sam Tolton's GNU awk answer,它有可能成为迄今为止最简单,最快速的解决方案。

使用专注于效果的变体来补充chepner's excellent answer

jq -Rrn '[inputs[:-1]|fromjson] | .properties.county + "|" + (.|tostring)' file |
  awk -F'|' '{ print $2 > ($1 "_bins_2016.json") }'

完全避免使用Shell循环,这样可以加快操作速度。

一般的想法是:

  • 使用jq修剪每个输入行的尾随,,将修剪后的字符串解释为JSON,提取县名称,然后输出修剪后的JSON字符串县名和不同的分隔符|

  • 使用awk命令将每一行拆分为前置县名和修剪后的JSON字符串,这允许awk轻松构造输出文件名并将JSON字符串写入其中。

注意:awk命令会保持所有输出文件处于打开状态,直到脚本完成,这意味着,在您的情况下,将同时打开100个输出文件 - 一个不应该的数字然而,这不是一个问题。

如果出现问题,您可以使用以下变体,其中jq首先按县名对行进行排序,然后允许awk立即关闭前一个输出字段在输入中到达下一个县:

jq -Rrn '
  [inputs[:-1]|fromjson] | sort_by(.properties.county)[] | 
    .properties.county + "|" + (.|tostring)
' file | 
   awk -F'|' '
    prevCounty != $1 { if (outFile) close(outFile); outFile = $1 "_bins_2016.json" }
    { print $2 > outFile; prevCounty = $1  }
  '

答案 2 :(得分:4)

更简单的chepner's answer版本:

while IFS= read -r line
do 
    countyName=$(jq --raw-output '.properties.county' <<<"${line: : -1}")
    jq <<< "${line: : -1}" >> "$countyName"_bins_2016.json
done<file

我们的想法是在从输入文件的每一行中删除jq后使用,过滤器过滤县名。然后该行以普通流的形式传递给jq以生成美化格式的JSON文件。

如果您来自相对较旧版本的bash(&lt; 4.0),请"${line%?}"使用"${line: : -1}"

例如,如果上述更改,您的某个县就会成为

cat ALAMANCE_bins_2016.json
{
  "type": "Feature",
  "properties": {
    "county": "ALAMANCE",
    "vBLA": 0,
    "vWHI": 0,
    "vDEM": 0,
    "vREP": 0,
    "vUNA": 0,
    "vTOT": 0
  },
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [
        [
          -79.527429,
          35.843303
        ],
        [
          -79.532428,
          35.843303
        ],
        [
          -79.532428,
          35.848302
        ],
        [
          -79.527429,
          35.848302
        ],
        [
          -79.527429,
          35.843303
        ]
      ]
    ]
  }
}

注意:当前的解决方案可能是性能密集型,因为逐行读取文件是一项昂贵的操作,同样为每一行调用jq

答案 3 :(得分:2)

这将做你想要的东西减去最后一个逗号: -

gawk 'match($0, /"county":"([^"]+)/, array){ print >array[1]"_bins_2016.json" }' INPUT_FILE

这将输出当前路径中的文件,文件名为COUNTRY NAME_bins_2016.json

该脚本逐行排列并使用正则表达式匹配确切的术语"country":",后跟一个或多个不是"的字符。它捕获引号中的字符,然后将其用作文件名的一部分,以将当前行附加到。

要删除当前路径中所有.json文件的尾随逗号,您可以使用: -

sed -i '$ s/,$//' *.json

如果您确定最后一个字符始终是逗号,则更快的解决方案是使用truncate: -

truncate -s-1 *.json

最后一部分取自这个答案:https://stackoverflow.com/a/40568723/1453798

答案 4 :(得分:1)

这是一个可以完成工作的快速脚本。它具有在大多数系统上工作的优点,而无需安装任何其他工具。

IFS=$'\n'
counties=( $( sed 's/^.*"county":"//;s/".*$//' counties.txt ) )
unset IFS

for county in "${!counties[@]}"
do
  county="${counties[$i]}"
  filename="$county".out.txt
  echo "'$filename'"
  grep "\"$county\"" counties.txt > "$filename"
done

将IFS设置为\n允许数组元素包含空格。 sed命令将所有文本删除到县名称的开头和之后的所有文本。 for循环是允许迭代数组的形式。最后,grep命令需要在搜索字符串周围加上双引号,以便作为其他县的子串的县不会意外地被放入错误的文件中。

有关详细信息,请参阅GNU BASH参考手册的this section