10

I understand how to use awk's printf function, but I don't want to specify every field.

For example, assume this is my file:

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

I want to format it so that every record's first field is the width of c11 -- the longest cell in the first field:

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

I understand that I could specify:

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

Let's assume I know what I want the width of the first column to be, but I do NOT know how many fields are in the file. Basically I want to do something like:

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

... and then print the rest of the fields in their original format.

1
  • Another way to address it: sed 's/|/'' '' '' |/;s/\(...\) */\1/' (here adding extra quotes to insert those 3 spaces as the SE comments squeeze contiguous spaces into one) Sep 11, 2019 at 16:23

4 Answers 4

14

You can use sprintf to re-format $1 only.

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
2
  • 1
    Concise, you can use dynamic formatting with sprintf too: E.g. awk -vf1=3 'BEGIN{OFS=FS="|"}{$1=sprintf("%-*s",f1,$1)}1' test.txt
    – Adam D.
    Sep 11, 2019 at 17:36
  • @A.Danischewski - Well, dang. I've been doing extensive awk programming for ~17 years, and have never come across that one before. To think of all the hassles it would have saved me. Sep 12, 2019 at 16:47
6

To figure out the largest/longest length of the first field, and then to reformat the values in the field according that length, you will have to do two separate passes over the file.

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

(note that the input file is specified twice on the command line)

For the data that you present, this would produce

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

The first pass is handled by the FNR == NR block, which simply keeps track of the longest field seen so far (m contains the maximum length seen), and skips to the next line.

The second pass is handled by the last block, which reformats the first field using sprintf(). The format string %-*s means "a left-justified string whose width is given by the integer argument before the argument that holds the actual string".

This could obviously be expanded to do all columns by turning the scalar m into an array that holds the maximum width of each column:

$ 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
0
1

The intelligent way is what steeldriver suggested. The needlessly convoluted way is to iterate over every field:

$ 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

But just sprintf $1 and be done with it.

1
  • 1
    You've got it a bit backwards, small concise statements generally are more convoluted. Iterating over the fields is less convoluted.
    – Adam D.
    Sep 11, 2019 at 19:40
1

In Awk you can use a "*" to generate a dynamic printf format string.

If you know the length already you can pass the field length for the first column with -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

Note: if you didn't know what the first column length is you could store the values in an array then finding the max col length along the way and print it all out in the END block.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.