使用動態列寬度和空白欄位解析輸出

使用動態列寬度和空白欄位解析輸出

驅動器有一個子命令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

您可以使用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每行為基礎使用該文件。每行(又稱記錄)稱為$_。另一個效果是-p它會在取得下一筆記錄之前自動列印目前記錄。
  • -l做 2 件事,組ORS = RS = \n
  • 正規表示式/\H+\h+(?=\H)/g應取得除最後一個字段之外的所有字段,然後將這些字段饋送到map.
  • map計算這些欄位的長度並為每個欄位加上前綴「A」。
  • 我們添加了一個包羅萬象的“A*”,而不是不選擇上面的最後一個字段。
  • 然後將它們傳遞給join使用空分隔符將它們粘在一起形成一個字串的字串。因此,解包格式已可供使用,並且由於//=運算子是defined-or函數而不會再次計算。
  • 現在,有了動態建立的解包格式,我們繼續應用於每一行,包括標題。
  • unpack使用提供的格式解包一個字串(在我們的例子中為當前行)並發出解包的欄位。
  • 然後將這些發出的欄位輸入到map其中,對每個欄位進行一一操作,並執行{ ... }程式碼中概述的步驟。在我們的例子中,我們在每個欄位中執行以下操作:a) 雙引號。 b) 用雙引號將該欄位括起來。
  • 編輯完字段後map,它將它們扔到join,它使用逗號將它們連接起來,形成一個漂亮的小CSV文件。
  • 附:請注意,我們不必修剪由 產生的欄位中的尾隨空白unpack,因為在使用(A 代表 ASCII)格式字元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,但需要兩次傳遞的方法,首先,使用輸入的標題行,我們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"

相關內容