Zmodyfikuj szerokość pierwszej kolumny w pliku za pomocą zmiennej liczby pól, używając awk

10

Rozumiem, jak korzystać z funkcji printf awk, ale nie chcę określać wszystkich pól.

Załóżmy na przykład, że to mój plik:

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

Chcę go sformatować tak, aby pierwszym polem każdego rekordu była szerokość c11 - najdłuższa komórka w pierwszym polu:

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

Rozumiem, że mógłbym określić:

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

Załóżmy, że wiem, jaka powinna być szerokość pierwszej kolumny, ale NIE wiem, ile pól jest w pliku. Zasadniczo chcę zrobić coś takiego:

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

... a następnie wydrukuj pozostałe pola w oryginalnym formacie.

Kayli O'Keefe
źródło
Innym sposobem rozwiązania tego problemu sed 's/|/'' '' '' |/;s/\(...\) */\1/'(tutaj dodając dodatkowe cytaty w celu wstawienia tych 3 spacji, gdy komentarze SE ściskają ciągłe spacje w jednym)
Stéphane Chazelas

Odpowiedzi:

14

Możesz użyć tylko sprintfdo ponownego sformatowania $1.

Dawny.

$ 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
steeldriver
źródło
Zwięzłe, można korzystać z formatowania dynamikę sprintf też: Npawk -vf1=3 'BEGIN{OFS=FS="|"}{$1=sprintf("%-*s",f1,$1)}1' test.txt
A.Danischewski
@ A.Danischewski - Cóż, cholera. Zajmuję się programowaniem awk od ~ 17 lat i nigdy wcześniej tego nie spotkałem. Gdybym pomyślał o wszystkich problemach, uratowałoby mnie to.
Paul Sinclair,
6

Aby obliczyć największą / najdłuższą długość pierwszego pola, a następnie sformatować wartości w polu zgodnie z tą długością, należy wykonać dwa osobne przejścia nad plikiem.

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

(zwróć uwagę, że plik wejściowy jest określony dwukrotnie w wierszu poleceń)

W przypadku danych, które przedstawisz, wygeneruje to

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

Pierwsze przejście jest obsługiwane przez FNR == NRblok, który po prostu śledzi najdłuższe widoczne pole ( mzawiera maksymalną widoczną długość) i przeskakuje do następnej linii.

Drugie przejście jest obsługiwane przez ostatni blok, który ponownie formatuje pierwsze pole za pomocą sprintf(). Łańcuch formatu %-*soznacza „ ciąg wyrównany do lewej, którego szerokość jest podawana przez argument liczby całkowitej przed argumentem zawierającym rzeczywisty ciąg”.

Można to oczywiście rozszerzyć na wszystkie kolumny, zmieniając skalar mw tablicę, która przechowuje maksymalną szerokość każdej kolumny:

$ 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
źródło
1

Inteligentny sposób sugeruje Steeldriver . Niepotrzebnie zawiłym sposobem jest iteracja po każdej dziedzinie:

$ 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

Ale słusznie sprintf $1i skończ z tym.

terdon
źródło
1
Masz to trochę wstecz, małe zwięzłe stwierdzenia są na ogół bardziej skomplikowane. Iteracja po polach jest mniej skomplikowana.
A.Danischewski
1

W Awk możesz użyć „*”, aby wygenerować ciąg formatu dynamicznego printf.

Jeśli znasz już długość, możesz podać długość pola dla pierwszej kolumny za pomocą opcji -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

Uwaga: jeśli nie wiesz, jaka jest długość pierwszej kolumny, możesz zapisać wartości w tablicy, a następnie znaleźć maksymalną długość kolumny po drodze i wydrukować ją w bloku END.

A.Danischewski
źródło