Key-Valueレコードを含むファイルをCSVに圧縮します。

Key-Valueレコードを含むファイルをCSVに圧縮します。

データパーサスクリプトを書きたいです。データ例は次のとおりです。

name: John Doe
description: AM
email: [email protected]
lastLogon: 999999999999999
status: active
name: Jane Doe
description: HR
email: [email protected]
lastLogon: 8888888888
status: active
...
name: Foo Bar
description: XX
email: [email protected]
status: inactive

キーと値のペアは常に同じ順序(name、、、、、、)ですがdescription、一部のフィールドが欠落emailしている可能性があります。最初の記録が完全であるという保証もありません。lastLogonstatus

予想される出力は、区切り文字で区切られた(例:CSV)値です。

John Doe,AM,[email protected],999999999999999,active
Jane Doe,HR,[email protected],8888888888,active
...
Foo Bar,XX,[email protected],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使用経験が十分ではありません。

ベストアンサー1

以下のプログラムが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,[email protected],999999999999999,active
Jane Doe,HR,[email protected],8888888888,active
Foo Bar,XX,[email protected],n/a,inactive

これにより、BEGINブロックでいくつかの初期化が実行されます。

  • 入力フィールド区切り文字を「aの:後にゼロ個以上のスペースが続く」に設定します。
  • 出力フィールド区切り文字を次のように設定します。,
  • フィールド名の配列を初期化します(静的アプローチを取り、リストをハードコードします)。

フィールドが見つかったら、nameそのフィールドがファイルの最初の行にあることを確認してからそうでない場合、以前に収集したデータを印刷します。次に、先ほど出会ったフィールドから始めて、配列の次のレコードの収集を開始しますcurrentname

他のすべての行の場合(単純化のために空白または注釈付きの行がないと仮定しますが、プログラムはこの行を自動的に無視する必要があります)、プログラムは行に記載されているフィールドを確認し、値を配列currentに保存します。現在のレコードの適切な位置にあります。

関数はprintrecこれらの配列を引数として使用し、実際の出力を実行します。欠落した値はn/a(または使用したい他の文字列)に置き換えられます。印刷後、アレイが次のデータセットを準備できるようにフィールドがクリアされます。

最後に、最後のレコードも印刷されます。

ノート

  1. ファイルの「値」部分に:-space-combinationsも含めることができる場合は、置き換えることでプログラムを強化できます。
    current[i]=$2
    
    渡す
    sub(/^[^:]*: */,"")
    current[i]=$0
    
    これは、行の最初の-spaceの組み合わせまでを含むすべての項目を:削除(sub)し、値を「行の最初の-spaceの組み合わせ以降のすべての項目」に設定します。:
  2. フィールドに出力区切り記号(例,)を含めることができる場合は、準拠したい規格に従って文字をエスケープしたり、出力を引用したりするための適切な措置を講じる必要があります。
  3. 正しく指摘したように、シェルループをテキスト処理ツールとして使用することはお勧めできません。より多くの内容を読みたい場合は、確認してください。このQ&A

おすすめ記事