動的な列幅と空のフィールドを含む出力を解析する

動的な列幅と空のフィールドを含む出力を解析する

gドライブlist次の例のようにファイルのリストを出力するサブコマンドがあります。

gdrive list

出力:

Id                                  Name                      Type   Size     Created
1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5   info.pdf                  bin    10.0 B   2018-08-27 20:26:20
1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl   2018-12-ss-scalettapass   dir             2018-08-27 20:26:19

などのツールを使用してこの出力を解析しようとしていますawkが、sed成功しません。

問題は、サイズ列の空の「フィールド」と列の動的な幅です。

この出力を解析する方法を知っている人はいますか?

答え1

awk は固定幅のデータを処理できます。まず、列幅を決定する必要があります。

fieldwidths=$(head -n 1 file | grep -Po '\S+\s*' | awk '{printf "%d ", length($0)}')

この値は"36 26 7 9 7 "、最後のフィールドが 7 文字より大きいです。任意に 70 文字にしてみましょう。

fieldwidths=${fieldwidths/% /0}

次に、データを読み取って CSV に変換します。

awk -v FIELDWIDTHS="$fieldwidths" '{
    for (i=1; i<=NF; i++) {
        val = $i
        sub(/ *$/, "", val)
        gsub(/"/, "\"\"", val)
        printf "%s\"%s\"", (i==1 ? "" : ","), val
    }
    print ""
}' file

出力:

"Id","Name","Type","Size","Created"
"1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5","info.pdf","bin","10.0 B","2018-08-27 20:26:20"
"1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl","2018-12-ss-scalettapass","dir","","2018-08-27 20:26:19"

Perlと同じ機能

perl -lne '
    if ($. == 1) {
        @head = ( /(\S+\s*)/g );
        pop @head;
        $patt = "^";
        $patt .= "(.{" . length($_) . "})" for @head;
        $patt .= "(.*)\$";
    }
    print join ",", map {s/"/""/g; s/\s+$//; qq("$_")} (/$patt/o);
' file

答え2

ヘッダー (1 行目) を調べて動的に展開テンプレートを作成することにより、関数をPerl使用してこれを実行できます。unpack

perl -lpe '
    $fmt //= join "", map("A" . length(), /\H+\h+(?=\H)/g), "A*";
    $_ = join ",", map { s/"/""/gr =~ s/(.*)/"$1"/r } unpack $fmt;
' input-file.txt

説明:

  • -pは、perlファイルを行ごとに消費します。各行、つまりレコードは と呼ばれます$_。 のもう 1 つの効果は、-p次のレコードを取得する前に現在のレコードを自動印刷することです。
  • -l2つのことを行い、設定するORS = RS = \n
  • 正規表現は/\H+\h+(?=\H)/g最後のフィールドを除くすべてのフィールドを取得し、それらを に渡しますmap
  • mapこれらのフィールドの長さを計算し、それぞれに「A」というプレフィックスを付けます。
  • 上記の最後のフィールドを選択しない代わりに、包括的な「A*」を追加します。
  • これらは に渡され、ヌル区切り文字を使用して文字列にまとめられます。そのため、アンパック形式はすぐに使用でき、関数である演算子joinによって再度計算されることはありません。//=defined-or
  • ここで、動的に作成された unpack 形式を使用して、ヘッダーを含むすべての行に適用していきます。
  • unpack指定された形式を使用して文字列 (この場合は現在の行) を展開し、展開されたフィールドを出力します。
  • 次に、これらの出力フィールドは に入力され、各フィールドmapが 1 つずつ処理され、{ ... }コードに概説されている手順が実行されます。この場合、各フィールドで次の操作を行います。a) 二重引用符を二重にします。b) フィールドを二重引用符で囲みます。
  • mapフィールドの編集が完了すると、それらのフィールドが に渡されjoin、コンマを使用して結合されて,小さなCSVファイルが作成されます。
  • 追伸:によって生成されたフィールドの末尾の空白を切り取る必要がないことに注意してください。これはunpack、 (ASCII の場合は A) 書式設定文字unpackを使用するときに、 によって自動的に切り取られるためですA

出力:

"Id","Name","Type","Size","Created"
"1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5","info.pdf","bin","10.0 B","2018-08-27 20:26:20"
"1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl","2018-12-ss-scalettapass","dir","","2018-08-27 20:26:19"

これはツールで実行できますsedが、2 パスのアプローチが必要になります。つまり、最初に入力のヘッダー行を使用してスクリプトをsed動的に生成し、次に入力ファイル (ヘッダーも含む) に対して操作を行って、目的の操作を実行します。

if="input-file.txt"
cmd=$(< "$if" head -n 1 | perl -lne 'print join $/, reverse map { $s += length();qq[s/./\\n/$s] } /\H+\h+(?=\H)/g')
sed -e '
    '"${cmd}"'
    s/"/""/g
    s/[[:blank:]]*\n/","/g
    s/.*/"&"/
' < "$if"

関連情報