Modifique el ancho de la primera columna en el archivo con un número variable de campos, usando awk

10

Entiendo cómo usar la función printf de awk, pero no quiero especificar cada campo.

Por ejemplo, suponga que este es mi archivo:

c1|c2|c3|c4|c5
c6|c7|c8|c9|c10
c11|c12|c13|c14|c15

Quiero formatearlo para que el primer campo de cada registro tenga el ancho de c11, la celda más larga en el primer campo:

c1 |c2|c3|c4|c5
c6 |c7|c8|c9|c10
c11|c12|c13|c14|c15

Entiendo que podría especificar:

awk -F"|" '{printf "%-3s%s%s%s%s\n", $1, $2, $3, $4, $5}' file > newfile

Supongamos que sé cuál quiero que sea el ancho de la primera columna, pero NO sé cuántos campos hay en el archivo. Básicamente quiero hacer algo como:

... '{printf "%-3s|", $1}'

... y luego imprima el resto de los campos en su formato original.

Kayli O'Keefe
fuente
Otra forma de abordarlo: sed 's/|/'' '' '' |/;s/\(...\) */\1/'(aquí agregando citas adicionales para insertar esos 3 espacios a medida que los comentarios de SE exprimen espacios contiguos en uno)
Stéphane Chazelas

Respuestas:

14

Puede usar solo sprintfpara volver a formatear $1.

Ex.

$ awk 'BEGIN{OFS=FS="|"} {$1 = sprintf("%-3s",$1)} 1' file
c1 |c2|c3|c4|c5
c6 |c7|c8|c9|c10
c11|c12|c13|c14|c15
conductor de acero
fuente
Conciso, también puede usar el formato dinámico con sprintf: por ejemploawk -vf1=3 'BEGIN{OFS=FS="|"}{$1=sprintf("%-*s",f1,$1)}1' test.txt
A.Danischewski, el
@ A.Danischewski - Bueno, maldita sea. He estado haciendo una extensa programación awk durante ~ 17 años, y nunca me había encontrado con eso antes. Pensar en todas las molestias me habría salvado.
Paul Sinclair
6

Para calcular la longitud más grande / más larga del primer campo, y luego formatear los valores en el campo de acuerdo con esa longitud, tendrá que hacer dos pases separados sobre el archivo.

awk 'BEGIN     { OFS = FS = "|" }
     FNR == NR { if (m < (n=length($1))) m = n; next }
               { $1 = sprintf("%-*s", m, $1); print }' file file

(tenga en cuenta que el archivo de entrada se especifica dos veces en la línea de comando)

Para los datos que presenta, esto produciría

c1 |c2|c3|c4|c5
c6 |c7|c8|c9|c10
c11|c12|c13|c14|c15

La primera pasada es manejada por el FNR == NRbloque, que simplemente realiza un seguimiento del campo más largo visto hasta ahora ( mcontiene la longitud máxima vista) y salta a la siguiente línea.

La segunda pasada es manejada por el último bloque, que formatea el primer campo usando sprintf(). La cadena de formato %-*ssignifica "una cadena justificada a la izquierda cuyo ancho viene dado por el argumento entero antes del argumento que contiene la cadena real".

Obviamente, esto podría expandirse para hacer todas las columnas al convertir el escalar men una matriz que contenga el ancho máximo de cada columna:

$ awk 'BEGIN     { OFS = FS = "|" }
       FNR == NR { for (i=1; i<=NF; ++i) if (m[i] < (n=length($i))) m[i] = n; next }
                 { for (i=1; i<=NF; ++i) $i = sprintf("%-*s", m[i], $i); print }' file file
c1 |c2 |c3 |c4 |c5
c6 |c7 |c8 |c9 |c10
c11|c12|c13|c14|c15
Kusalananda
fuente
1

La forma inteligente es lo que sugirió Steeldriver . La forma complicada innecesaria es iterar sobre cada campo:

$ awk -F'|' '{printf "%-3s|",$1; for(i=2;i<NF;i++){printf "%s|",$i} printf "%s\n", $i}' file
c1 |c2|c3|c4|c5
c6 |c7|c8|c9|c10
c11|c12|c13|c14|c15

Pero solo sprintf $1y listo.

terdon
fuente
1
Lo tienes un poco al revés, las pequeñas declaraciones concisas generalmente son más complicadas. Iterar sobre los campos es menos complicado.
A.Danischewski
1

En Awk puede usar un "*" para generar una cadena dinámica de formato printf.

Si ya conoce la longitud, puede pasar la longitud del campo para la primera columna con -v.

awk -vcol1=3 'BEGIN{FS="|"}{for(i=1;i<=NF;i++){if(i==1)printf "%*-s%s",col1,$i,FS;else if(i!=NF)printf "%s%s",$i,FS;else printf "%s\n",$i;};}' test.txt

Nota: si no sabía cuál es la longitud de la primera columna, puede almacenar los valores en una matriz y luego encontrar la longitud máxima de la columna en el camino e imprimirla en el bloque FIN.

A.Danischewski
fuente