Analizar la salida con anchos de columna dinámicos y campos vacíos

Analizar la salida con anchos de columna dinámicos y campos vacíos

gdrivetiene un subcomando listque imprime una lista de archivos como el siguiente ejemplo:

gdrive list

Producción:

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

Estoy intentando analizar este resultado usando herramientas como awky sedsin éxito.

Los problemas son 'campos' vacíos en la columna de tamaño y los anchos dinámicos de las columnas.

¿Alguien tiene una idea de cómo analizar este resultado?

Respuesta1

awk puede manejar datos de ancho fijo. Primero necesitamos determinar el ancho de las columnas:

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

Este valor es "36 26 7 9 7 ": el último campo tiene más de 7 caracteres. Hagamos arbitrariamente 70 caracteres:

fieldwidths=${fieldwidths/% /0}

Ahora, leamos los datos y convirtámoslos en 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

salidas:

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

La misma funcionalidad con 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

Respuesta2

Puede hacer esto Perlusando la unpackfunción creando la plantilla de descompresión dinámicamente examinando el encabezado (primera línea):

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

Explicación:

  • -phará que perlse consuma el archivo por línea. Cada línea, también conocida como registro, se denomina $_. Otro efecto -pes que imprime automáticamente el registro actual antes de buscar el siguiente.
  • -lhace 2 cosas, estableceORS = RS = \n
  • La expresión regular /\H+\h+(?=\H)/gbuscará todos los campos excepto el último y luego estos se enviarán a map.
  • mapcalcula las longitudes de estos campos y antepone una "A" a cada uno.
  • En lugar de no seleccionar el último campo anterior, agregamos una "A*" general.
  • Luego, estos se pasan a joinquien los une en una cadena usando el delimitador nulo. Entonces, el formato de descomprimir está listo para usar y no se vuelve a calcular debido al //=operador que es la defined-orfunción.
  • Ahora, armados con el formato de descomprimir creado dinámicamente, procedemos a aplicarlo a cada línea, incluido el encabezado.
  • unpackdescomprime una cadena, en nuestro caso la línea actual, usando el formato proporcionado y emite campos desempaquetados.
  • Estos campos emitidos luego se ingresan, mapque opera en cada uno uno por uno y realiza los pasos descritos en el { ... }código. En nuestro caso, en cada campo hacemos lo siguiente: a) duplicar las comillas dobles. b) cubra el campo con comillas dobles.
  • Una vez que maptermina de editar los campos, los envía a join, que los une usando la coma ,para formar un pequeño archivo agradable CSV.
  • PD:Tenga en cuenta que no tuvimos que recortar los espacios en blanco finales en los campos generados por unpack, porque unpacklo hace por usted cuando usa el Acarácter de formato (A para ASCII).

Producción:

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

Esto lo puede hacer la sedherramienta, pero necesitaría un enfoque de dos pasos, en el que primero, usando la línea de encabezado de la entrada, generamos un sedscript dinámicamente, que luego opera sobre el archivo de entrada (incluido también el encabezado) para realice la operación deseada, como se muestra:

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"

información relacionada