![](/img/trans.png)
[英]Issue with using While loop in bash script (to split a file into multiple files)
[英]Bash/*NIX: split a file into multiple files on a substring
之前已經問過並回答過這個問題的變種,但是我發現我的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中這樣做,如果這很重要的話。 我希望通過研究你能提出的任何解決方案來學到很多東西,所以如果你想花時間解釋'為什么'以及那些將會很棒的'什么'。 謝謝!
編輯,以明確有不同的縣名,其中一些是雙字名。
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
[inputs[:-1]|fromjson]
以字符串形式讀取文件的每一行, [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循環。)
如果使用字符串解析而不是正確的JSON解析來提取縣名是可以接受的 - 一般來說很脆弱,但是在這個簡單的情況下可以工作 - 考慮一下Sam Tolton的GNU awk
答案 ,它有可能成為迄今為止最簡單,最快速的解決方案。
通過專注於性能的變體來補充chepner的出色答案 :
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 }
'
更簡潔的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
(< 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
。
這將做你想要的東西減去最后一個逗號: -
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
這是一個可以完成這項工作的快速腳本。 它具有在大多數系統上工作的優點,而無需安裝任何其他工具。
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參考手冊的此部分 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.