使用动态列宽度和空字段解析输出

使用动态列宽度和空字段解析输出

驱动器有一个子命令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

您可以使用Perlunpack函数通过检查标题(第一行)动态创建解包模板来完成此操作:

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"

相关内容