Bash

帶有鍵值記錄的壁球文件到 CSV

  • June 23, 2021

我想寫一個數據解析器腳本。範例數據為:

name: John Doe
description: AM
email: john@doe.cc
lastLogon: 999999999999999
status: active
name: Jane Doe
description: HR
email: jane@doe.cc
lastLogon: 8888888888
status: active
...
name: Foo Bar
description: XX
email: foo@bar.cc
status: inactive

鍵值對總是按相同的順序排列(name, description, email, lastLogon, status),但有些欄位可能會失去。也不保證第一條記錄是完整的。

預期的輸出是分隔符分隔的(例如 CSV)值:

John Doe,AM,john@doe.cc,999999999999999,active
Jane Doe,HR,jane@doe.cc,8888888888,active
...
Foo Bar,XX,foo@bar.cc,n/a,inactive

我的解決方案是使用 whileread循環。我的腳本的主要部分:

while read line; do
   grep -q '^name:' <<< "$line" && status=''
   case "${line,,}" in
       name*) # capture value ;;
       desc*) # capture value ;;
       email*) # capture value ;;
       last*) # capture value ;;
       status*) # capture value ;;
   esac

   if test -n "$status"; then
       printf '%s,%s,%s,%s,%s\n' "${name:-n\a}" ... etc ...
       unset name ... etc ...
   fi
done < input.txt

這行得通。但顯然,非常緩慢。703行數據的執行時間:

real    0m37.195s
user    0m2.844s
sys     0m22.984s

我正在考慮這種awk方法,但我沒有足夠的經驗使用它。

以下awk程序應該可以工作。理想情況下,您會將其保存到單獨的文件中(例如squash_to_csv.awk):

#!/bin/awk -f

BEGIN {
   FS=": *"
   OFS=","
   recfields=split("name,description,email,lastLogon,status",fields,",")
}

function printrec(record) {
   for (i=1; i<=recfields; i++) {
   if (record[i]=="") record[i]="n/a"
   printf "%s%s",record[i],i==recfields?ORS:OFS;
   record[i]="";
   }
}
   
$1=="name" && (FNR>1) { printrec(current) }

{
   for (i=1; i<=recfields;i++) {
       if (fields[i]==$1) {
           current[i]=$2
           break
       }
   }
}

END {
   printrec(current)
}

然後,您可以將其稱為

awk -f squash_to_csv.awk input.dat
John Doe,AM,john@doe.cc,999999999999999,active
Jane Doe,HR,jane@doe.cc,8888888888,active
Foo Bar,XX,foo@bar.cc,n/a,inactive

這將在BEGIN塊中執行一些初始化:

  • 將輸入欄位分隔符設置為“a:後跟零個或多個空格”
  • 將輸出欄位分隔符設置為,
  • 初始化欄位名稱數組(我們採用靜態方法並對列表進行硬編碼)

如果name遇到該欄位,它將檢查它是否在文件的第一行,如果不是,則列印之前收集的數據。然後它將開始收集數組中的下一條記錄current,從name剛剛遇到的欄位開始。

對於所有其他行(為簡單起見,我假設沒有空行或註釋行 - 但話又說回來,這個程序應該默默地忽略這些行),程序檢查行中提到了哪些欄位,並將值儲存在current用於目前記錄的數組中的適當位置。

該函式printrec將這樣的數組作為參數並執行實際輸出。缺失值被替換為n/a(或您可能想要使用的任何其他字元串)。列印後,這些欄位被清除,以便數組為下一組數據做好準備。

最後,還列印最後一條記錄。

筆記

  1. 如果文件的“值”部分還可以包含:-space-combinations,則可以通過替換來強化程序
current[i]=$2

經過

sub(/^[^:]*: */,"")
current[i]=$0

這會將值設置為“第一個:-space 組合之後的所有內容”,方法是刪除 ( sub) 所有內容,直到包括該:行的第一個 -space-combination。 2. 如果任何欄位可以包含輸出分隔符(在您的範例中,),則必須採取適當的措施來轉義該字元或引用輸出,具體取決於您要遵守的標準。 3. 正如您正確指出的那樣,非常不鼓勵將 shell 循環作為文本處理工具。如果您有興趣閱讀更多內容,可以查看此問答

引用自:https://unix.stackexchange.com/questions/655262