Printing Fixed Width Columns

Hi everyone,

I have been working on a pretty laborious shellscript (with bash) the last couple weeks that parses my firewall policies (from a Juniper) for me and creates a nifty little columned output. It does so using awk on a line by line basis to pull out the appropriate pieces of each policy to build the proper output later.

Unfortunately, not all the data that I extract from the text file comes out to the same length so often times my columns are all over the place.

I've been searching left and right, but cannot find of a good simple way to do this. I am beginning to think I will have to create some sort of function to find the length of each individual column before printing the data, but I am not sure if this is possible.

I am currently using the following code:

echo -e "From: \t"$frm_Zone"\tTo:\t"$to_Zone
echo -e $pol_Id"\t"$src_Address"\t\t"$dst_Address"\t\t"$pol_Service"\t"$pol_Action"\t"$pol_Name

#finding the array with the largest index
#so we now how many times to run the for loop

lnAddy=`echo ${#srcAddyArray
[*]}`
lnSrc=`echo ${#dstAddyArray
[*]}`
lnSvc=`echo ${#serviceArray
[*]}`

max2 $lnAddy $lnSrc
temp=$?
max2 $temp $lnSvc
longest=$?

for (( i = 0; i < longest; i++ )) do

echo -e "\t"${srcAddyArray[$i]}"\t"${dstAddyArray[$i]}"\t"${serviceArray[$i]}
done

The output unfortunately ends up looking something like this:

From:   Trust-cs        To:     Untrust-Firn
615     Any             mail.acast.foo.com             tcp-6777 (wo 85073)     Permit Nat SRC  wo 85073
        69.xxx.xxx.1    mail.gort.org tcp-6788 (wo 85073)
        75.123.44.1     my.mctabbs.net   tcp-8008 (wo 85073)

From:   Trust-cs        To:     DMZ-test
596     Any             69.xxx.xxx.31            TCP-15888       Permit
        tables.noc.wow.org     mages.icecrown.com      tcp-16888
                        TCP-17888
                        UDP-17888
                        UDP-18888

Is it possible to get the output to be spaced appropriately so it doesn't get clobbered if some lines are longer than others?

Any help would be greatly appreciated! If you need to see the rest of the script or some sample data, please let me know and I'll be happy to assist.

Thanks in advance!

Hi, cixelsyd:

Welcome to the forums. I believe what you want to check out is printf. It is usually a shell built-in, but is probably also available as a standalone executable if not builtin. printf is also available as a function within AWK. You can use it to print out fixed width columns regardless of the string argument's length.

An example showing how to print out two 10 character wide, left-justified columns (separated by a space):

$ one=1 four=4444 six=666666 nine=999999999
$ echo $one $four; echo $six $nine
1 4444
666666 999999999
$ printf '%-10s %-10s\n' $one $four; printf '%-10s %-10s\n' $six $nine
1          4444      
666666     999999999

Note that if the string is wider than the column's width (in this case 10), the column will expand to accomodate the data and break the formatting. So, you'll need to choose a width that should seldom if ever be exceeded, or truncate the string to 10 prior to passing it to printf.

In bash, you should definitely have printf builtin. Perhaps something similar to the following will work for you:

printf '\t%-25s %-25s %-25s\n' "${srcAddyArray[$i]}" "${dstAddyArray[$i]}" "${serviceArray[$i]}"

Regards,
Alister

Alister,

Thank you so much for your help! That did exactly the trick. I modified the script to the following:

echo -e "From: \t"$frm_Zone"\tTo:\t"$to_Zone
                        
printf '%-5s %-20s %-20s %-20s %-15s %-10s\n' $pol_Id $src_Address $dst_Address "$pol_Service" "$pol_Action" "$pol_Name"

#finding the array with the largest index
#so we now how many times to run the for loop

lnAddy=`echo ${#srcAddyArray
[*]}`
lnSrc=`echo ${#dstAddyArray
[*]}`
lnSvc=`echo ${#serviceArray
[*]}`

max2 $lnAddy $lnSrc
temp=$?
max2 $temp $lnSvc
longest=$?

for (( i = 0; i < longest; i++ )) do

              printf '%-5s %-20s %-20s %-20s\n' "" "${srcAddyArray[$i]}" "${dstAddyArray[$i]}" "${serviceArray[$i]}"

done

I ended up having to print one blank column so it lined up properly, but now the output is exactly how I wanted:

From:   Trust-cs        To:     Untrust-Firn
615   Any                  mail.acast.xxxx.xxx  tcp-6777 (wo 85073)  Permit Nat SRC  wo 85073  
      69.xx.xxx.1          mail.xxxxxxx.edu     tcp-6788 (wo 85073) 
      75.xxx.44.1          my.xxxxxx.edu        tcp-8008 (wo 85073) 

From:   Trust-cs        To:     DMZ-acad
596   Any                  69.xx.xxx.31         TCP-15888            Permit                    
      tables.noc.xxxx.xxx  mages.icecrown.com   tcp-16888           
                                                TCP-17888           
                                                UDP-17888           
                                                UDP-18888           

Thank you so much for your help!

You're very welcome.

Cheers,
Alister

This makes sense.

$ diff newfile oldfile
2,3c2
<
< printf '%-5s %-20s %-20s %-20s %-15s %-10s\n' $pol_Id $src_Address $dst_Address "$pol_Service" "$pol_Action" "$pol_Name"
---
> echo -e $pol_Id"\t"$src_Address"\t\t"$dst_Address"\t\t"$pol_Service"\t"$pol_Action"\t"$pol_Name
19,20c18
<               printf '%-5s %-20s %-20s %-20s\n' "" "${srcAddyArray[$i]}" "${dstAddyArray[$i]}" "${serviceArray[$i]}"
<
---
> echo -e "\t"${srcAddyArray[$i]}"\t"${dstAddyArray[$i]}"\t"${serviceArray[$i]}