Printf in Awk with the Same Space Between Columns

Printf in Awk with the Same Space Between Columns

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
4

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 column on MacOS does not have the -R option. Have to do rev file | column -t | rev on a mac

2

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

James H. Sterling
Author

James H. Sterling

James Sterling reports on renewable energy developments, climate policy, ecological conservation, and green tech innovations around the globe.