此问题的变体之前已被问及并得到解答,但我发现我的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中这样做,如果这很重要的话。我希望通过研究你可以建议的任何解决方案来学到很多东西,所以如果你想花时间解释为什么'以及'什么'这太棒了。谢谢!
已编辑,以明确有不同的县名,其中一些是双字名。
答案 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。