簡體   English   中英

Bash / * NIX:將文件拆分為子字符串上的多個文件

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM