I wrote a C tool for this and similar tasks (still needs tr to delete .), but use pipes! Option it to pad with 0, tab on ',' and right justify the numbers. Its original use was to align tab separated columns in bulk data (assuming fixed pitch font):
$ cat mysrc/autotab.c
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
static FILE *tmp ; /* temp file */
static char *just = "l" ; /* output column justification */
static char *osep = " " ; /* output column sep */
static char j ; /* current justification */
static char sav[4096] ; /* output column store for justification */
static char savl[65536];/* output line store */
static int c ; /* character read */
static int cl = 0 ; /* current column length */
static int col = 0 ; /* current column # */
static int fs = 0 ; /* possibly embedded spaces found */
static int gen_hdr = 0 ; /* generate header state */
static int i ; /* utility int */
static int no ; /* narrow, overlap final column */
static int isep = '\t' ; /* input column sep */
static int jlen = 1 ; /* output column justification */
static int l[4096] ; /* array of column widths */
static int ll = 0 ; /* output line length */
static int maxcol = 0 ; /* max column # */
int main( int argc, char **argv ){
for ( i = 1 ; i < argc ; i++ ){
if ( !strcmp( argv, "-is" ) && ( i + 1 ) < argc ){
isep = argv[++i][0] ;
continue ;
}
if ( !strcmp( argv, "-os" ) && ( i + 1 ) < argc ){
osep = argv[++i] ;
continue ;
}
if ( !strcmp( argv, "-no" )){
no = 1 ;
continue ;
}
if ( !strcmp( argv, "-j" ) && ( i + 1 ) < argc ){
just = argv[++i] ;
jlen = strlen( just );
continue ;
}
if ( !strcmp( argv, "-gh" ) ){
gen_hdr = 1 ;
continue ;
}
fprintf( stderr,
"\n"
"Usage: autotab [ -is <i_sep> ] [ -os <o_sep> ] [ -gh ] [ -no ] [ -j <just> ]\n"
"\n"
"Scans input as columns defined by <i_sep> (default tab), measuring maximum\n"
"column width without blank padding and saving the input. Lines with no\n"
"<i_sep> are not measured. If -no is present (narrow, overlapping), the\n"
"characters between the last <i_sep> on a line and the line feed are not\n"
"measured. (The -no option is only useful with left justification.)\n"
"After reading EOF, the saved input is printed, padded to the measured\n"
"column width and separated by the <o_sep> string (default 2 spaces) with\n"
"empty right side columns, blanks and carriage returns suppressed.\n"
"If -j is present, the characters of <just> define the justification of each\n"
"column with the same relative offset:\n"
" r for right, c for centered, and anything else means left.\n"
"If -gh is present, the saved input is prefixed by a numbered column header,\n"
"which is padded and aligned like the data.\n"
"The size limits are: %d measured columns, output line %d characters\n"
"and right or center justified column data width %d characters.\n"
"\n",
sizeof( l )/sizeof(int),
sizeof( savl ),
sizeof( sav ));
exit( 1 );
}
if ( NULL == ( tmp = tmpfile() )){
perror( "tmpfile()" );
exit( 1 );
}
memset( (char*)l, 0, sizeof( l ) );
do {
switch( c = getchar() ){
case EOF:
if ( ferror( stdin ) ){
perror( "stdin" );
exit( 1 );
}
continue ; /* Out of loop */
case '\n':
if ( no ){
col = 0 ;
cl = 0 ;
fs = 0 ;
break ;
}
/* Intentional Fall Through */
case '\f':
if ( cl && col ){
if ( col == ( sizeof( l ) / sizeof( int ) )){
fprintf( stderr,
"Too many columns!\n" );
exit( 1 );
}
if ( cl > l[col] ){
l[col++] = cl ;
}
}
if ( col > maxcol ){
maxcol = col ;
}
col = 0 ;
cl = 0 ;
fs = 0 ;
break ;
case ' ':
if ( cl ){
fs++ ;
}
case '\r':
continue ;
default:
if ( c == isep ){
if ( cl ){
if ( col == ( sizeof( l )
/ sizeof( int ) )){
fprintf( stderr,
"Too many columns!\n"
);
exit( 1 );
}
if ( cl > l[col] ){
l[col] = cl ;
}
}
col++ ;
cl = 0 ;
fs = 0 ;
break ;
}
cl++ ;
cl += fs ;
while ( fs ){
fs-- ;
if ( EOF == putc( ' ', tmp )){
perror( "putc(tmp)" );
exit( 1 );
}
}
break ;
}
if ( EOF == putc( c, tmp )){
perror( "putc(tmp)" );
exit( 1 );
}
} while ( c != EOF );
rewind( tmp );
if ( gen_hdr ){
col = 0 ;
do {
if ( 0 > ( cl = printf( "Col. %d", col + 1 ))){
if ( ferror( stdout )){
perror( "stdout" );
exit( 1 );
}
exit( 0 );
}
if ( cl > l[col] ){
l[col] = cl ;
} else while ( cl++ < l[col] ){
if ( EOF == putchar( ' ' )){
if ( ferror( stdout )){
perror( "stdout" );
exit( 1 );
}
exit( 0 );
}
}
if ( ++col == maxcol ){
break ;
}
if ( EOF == fputs( osep, stdout )){
if ( ferror( stdout )){
perror( "stdout" );
exit( 1 );
}
exit( 0 );
}
} while ( 1 );
if ( EOF == putchar( '\n' )){
if ( ferror( stdout ) ){
perror( "stdout" );
exit( 1 );
}
exit( 0 );
}
cl = col = 0 ;
}
j = *just ;
do {
switch ( c = getc( tmp )){
case EOF:
if ( ferror( tmp )){
perror( "getc(tmp)" );
exit( 1 );
}
if ( !ll && !cl ){
exit( 0 );
}
c = '\n' ;
/* Intentional fall through for EOF as linefeed */
case '\f':
case '\n':
if ( cl ){
if ( col ){
fs = l[col] - cl ;
} else {
fs = 0 ;
}
switch ( j ){
case 'c':
fs >>= 1 ;
case 'r':
if ( ll > ( sizeof( savl ) - fs - cl )){
fputs(
"Output line too long!\n", stderr );
exit( -1 );
}
ll += sprintf( savl + ll,
"%*s%.*s",
fs,
"",
cl,
sav );
break ;
}
}
while ( savl[--ll] == ' '
|| savl[ll] == '\t' ){
/* nothing */
}
if ( 0 > printf( "%.*s%c", ++ll, savl, c )){
if ( ferror( stdout )){
perror( "stdout" );
}
exit( 1 );
}
ll = 0 ;
col = 0 ;
cl = 0 ;
fs = 0 ;
j = *just ;
break ;
default:
if ( c == isep ){
fs = l[col] - cl ;
if ( ll >
( sizeof( savl ) - fs - cl - strlen( osep ))){
fputs(
"Output line too long!\n", stderr );
exit( 1 );
}
switch ( j ){
case 'c':
ll += sprintf( savl + ll,
"%*s%.*s%*s",
fs >> 1,
"",
cl,
sav,
fs - ( fs >> 1 ),
"" );
break ;
case 'r':
ll += sprintf( savl + ll,
"%*s%.*s",
fs,
"",
cl,
sav );
break ;
default:
ll += sprintf( savl + ll, "%*s", fs, ""
);
break ;
}
ll += sprintf( savl + ll, "%s", osep );
if ( ++col < jlen ){
j = just[col] ;
} else {
j = 'l' ;
}
fs = 0 ;
cl = 0 ;
continue ;
}
if ( j == 'r' || j == 'c' ){
if ( cl >= sizeof( sav )){
fprintf( stderr,
"\nFatal: Column %d too wide.\n",
++col );
exit( 1 );
}
sav[cl++] = c ;
continue ;
}
if ( ll >= sizeof( savl )){
fprintf( stderr, "Output line too long!\n" );
exit( 1 );
}
cl++ ;
savl[ll++] = c ;
break ;
}
} while ( c != EOF );
exit( 0 );
}