I have a data file with five columns, when I use the printf command in awk, the output isn't aligned.
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
I need to use all columns with the same space separator, independent is the number are tens, hundreds, or thousands like this:
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
How can I do this using printf in awk?
I'm using this:
awk '{printf "%d %s %d %s %d %s %d %s %d\n", $1,"",$2,"",$3,"",$4,"",$5}' test
5 Answers
Use column on the output rather than trying to format it with awk:
$ column -t -R'1,2,3,4,5' file
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
Your version of column may already support -R0 which means "right align all columns" so you don't need to list them, see .
As @glennjackman pointed out in comments:
The BSD-derived
columnon MacOS does not have the-Roption. Have to dorev file | column -t | revon a mac
Most printf formats allow the width to be supplied as an argument to a * format specifier, eg:
printf "%*s", 5 "abc"
Is evaulated as:
printf "%5s, "abc"
One awk idea making use of this printf/* feature:
awk '
FNR==NR { for (i=1;i<=NF;i++)
w[i]= length($i) > w[i] ? length($i) : w[i] # find max width for each column
next
}
{ pfx=""
for (i=1;i<=NF;i++) {
printf "%s%*s", pfx, w[i], $i
pfx=" " # (aligned) column delimiter == 2 spaces for columns 2 to NF
}
print "" # terminate current line
}
' five.dat five.dat
NOTES:
- requires 2 passes of the input file (could be rewritten to use a single pass but will need to store the entire file in memory)
- assumes the minimum delimiter between (aligned) columns is 2 spaces
This generates:
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
The 3 easiest solution are: 1) pipe the output to column -t, 2) use a tab separator (doesn't completely align the text, but for the sample input is sufficient, and 3) print each column on a fixed width.
$ cat input
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
$ awk '($1=$1) || 1' OFS=\\t input
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
$ awk '{printf "%5s%5s%5s%5s%5s\n", $1, $2, $3, $4, $5}' input
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
Here's one that requires two passes of the data (hence the file file in the end):
$ awk 'NR==FNR { # first pass
for(i=1;i<=NF;i++)
if(m[i]=="" || m[i]<length($i)) # get max field widths
m[i]=length($i)
next
}
{ # second pass
for(i=1;i<=NF;i++)
printf "%" m[i] "s%s",$i,(i==NF?ORS:" ") # output two spaces in between
}' file file # two passes, twice the file
Output:
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
Here is an awk to do that:
awk 'FNR==NR{for(i=1;i<=NF;i++) if (w[i]<length($i)) w[i]=length($i); next}
{for(i=1;i<=NF;i++) printf("%*s%s", w[i], $i, i<NF ? OFS : ORS)}
' file file
Prints:
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0
Then if you want to field size to increase, just add that:
awk -v wp=5 'FNR==NR{for(i=1;i<=NF;i++) if (w[i]<length($i)+wp) w[i]=length($i)+wp; next}
{for(i=1;i<=NF;i++) printf("%*s%s", w[i], $i, i<NF ? OFS : ORS)}
' file file
118 96 105 106 0
119 97 106 107 0
120 98 107 108 0
121 99 108 109 0
122 100 109 110 0
123 101 110 111 0
124 102 111 23 0
125 11 12 112 0
126 103 112 113 0
127 104 113 114 0
128 105 114 115 0