Bash/*NIX:将文件拆分为子字符串上的多个文件
这个问题的各种变体以前都被问过并回答过,但我发现我的sed/grep/awk技能太初级,无法从这些技能转换为自定义解决方案,因为我几乎从未使用shell脚本 我有一个相当大的(100K+行)文本文件,其中每行定义一个GeoJSON对象,每个这样的对象包括一个名为“county”的属性(总共有100个不同的county)。下面是一个片段:Bash/*NIX:将文件拆分为子字符串上的多个文件,bash,macos,shell,geojson,Bash,Macos,Shell,Geojson,这个问题的各种变体以前都被问过并回答过,但我发现我的sed/grep/awk技能太初级,无法从这些技能转换为自定义解决方案,因为我几乎从未使用shell脚本 我有一个相当大的(100K+行)文本文件,其中每行定义一个GeoJSON对象,每个这样的对象包括一个名为“county”的属性(总共有100个不同的county)。下面是一个片段: {"type": "Feature", "properties": {"county":"ALAMANCE", "vBLA": 0, "vWHI": 4, "v
{"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是县名)。我还希望每个文件末尾的最后一个字符(逗号)消失
我在MacOSX上做这件事,如果这有关系的话。我希望通过研究你能提出的任何解决方案,能学到很多东西,所以如果你想花时间解释“为什么”和“什么”,那就太棒了。谢谢
编辑以明确有不同的县名称,其中一些是两个单词的名称。jq
可以这样做;它可以将输入和输出分组,每组一行文本。然后,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
无需shell循环。)更简单的版本:
注意:当前的解决方案可能是性能密集型的,因为逐行读取文件是一项昂贵的操作,并且对每一行调用
jq
。这将实现您想要的功能,减去去掉最后一个逗号:-
gawk'match($0,/“country”:“([^”]+)/,数组){print>array[1]“\u bins\u 2016.json”}”输入文件
这将输出当前路径中的文件,文件名格式为COUNTRY NAME\u bins\u 2016.json
脚本逐行使用正则表达式来匹配确切的术语“country”:“
,后跟1个或多个非”字符。它捕获引号中的字符,然后将其用作文件名的一部分,将当前行附加到其中
要从当前路径中的所有.json文件中删除尾随逗号,可以使用:-
sed-i'$s/,$/'*.json
如果确定最后一个字符始终是逗号,则更快的解决方案是使用truncate:-
truncate-s-1*.json
这个答案的最后一部分:这里有一个快速脚本可以完成这项工作。它的优点是可以在大多数系统上工作,而无需安装任何其他工具
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
允许数组元素包含空格。sedsed
命令将删除县名称开头之前的所有文本以及县名称后面的所有文本。for
循环是允许在数组上迭代的形式。最后,grep
命令需要在搜索字符串周围加上双引号,这样作为其他县的子字符串的县就不会意外地被放入错误的文件中
参阅GNU BASH参考手册以获取更多信息。
< P>如果使用字符串解析而不是适当的JSON解析来提取县名是可接受的——一般是易碎的,但在这个简单的情况下,可以考虑,它有可能是迄今为止最简单和最快的解决方案。
以注重性能的变体作为补充:
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 }
'
在您的示例中,所有行都使用countyALAMANCE
?Nope命名。有100个不同的县名。有些是两个单词的名字,但都用引号括起来了。你应该试试看。因此,这不是一个脚本编写服务。如前所述,这不是一个真正合适的问题。@user1661497:“country”
部分是否代表县名?如果它们在示例中都是相同的,那么您希望如何从中创建唯一的文件?用你的实际线路更新你的输入样本,我听到了。但到目前为止,我的努力只是产生了垃圾……可能是因为我真的对正则表达式感到困惑,不知道该逃避什么角色,什么时候逃避。不幸的是,到目前为止,我的“刺杀”除了流血之外什么也没做。我简直不敢相信我有着完全相同的逻辑。你认为我应该保留它(或者)你的更结实?你的更简单;我的效率更高,因为我只打开每个输出文件一次。(假设数据分布均匀,每个县大约有1000行输出,因此您必须打开每个输出文件1000次。)天哪。我完全不知道jq。只需快速浏览一下git,它可能就是我梦想的答案,因为我发现与JSON一起使用sed/grep/awk简直是噩梦。非常感谢你@user1661497:使用这个答案,它会更加健壮
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
jq -Rrn '[inputs[:-1]|fromjson] | .properties.county + "|" + (.|tostring)' file |
awk -F'|' '{ print $2 > ($1 "_bins_2016.json") }'
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 }
'