Анализ вывода с динамической шириной столбцов и пустыми полями

Анализ вывода с динамической шириной столбцов и пустыми полями

gdriveимеет подкоманду 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функции, создав шаблон распаковки динамически, проверив заголовок (1-я строка):

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файл.
  • P.S.:Обратите внимание, что нам не пришлось обрезать конечные пробелы в полях, сгенерированных unpack, так как , unpackделает это за вас при использовании Aсимвола форматирования (A для ASCII).

Выход:

"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"

Связанный контент