bashformattingprintftput

How to format columns with printf in bash when tput is used


Using tput changes the length of the string and so columns are not aligned. How to fix this?

Tried following code in bash script.

B="$(tput bold)" # Bold text
N="$(tput sgr0)" # Normal text

function testing(){

IN="KEYS | VALUES | DESCRIPTION
id | ${B}10${N} or ${B}20${N} | Enter ID. Default is ${B}10${N}.
status | ${B}true${N} or ${B}false${N} | Enter status of something. Default is ${B}true${N}.
style | Example: Standard | Give suitable standard."

    IFS= 
    while read -r lines; do
        IFS='|' read -r -a array <<< "$lines"
        printf "%-35s %-35s %-s\n" "${array[0]}" "${array[1]}" "${array[2]}"
    done <<< "$IN"

    read -p "$*"
    exit 0
}

Output is something like:

    KEYS          VALUES                              DESCRIPTION
    id            **10** or **20**           Enter ID. Default is **10**.
    status        **true** or **false**      Enter status of something. Default is **true**.
    style         Example: Standard                   Give suitable standard.

Expected to be:

    KEYS          VALUES                              DESCRIPTION
    id            **10** or **20**                    Enter ID. Default is **10**.
    status        **true** or **false**               Enter status of something. Default is **true**.
    style         Example: Standard                   Give suitable standard.

Solution

  • As I mentioned in my comment:

    What if we measure the length of the second field - with and without the special chars - and then use the difference (ie, the number of non-printable characters) to increase the format width of the second field of the printf command?

    We'll store the length of the second field (which includes our special characters) in a new variable len2:

    len2="${#array[1]}"
    

    Next we want to strip out the special characters and measure the length of the resulting string and place in variable lenx:

    x="${array[1]//${B}/}"    # strip out all occurrences of variable 'B'
    x="${x//${N}/}"           # strip out all occurrences of variable 'N'
    lenx=${#x}
    

    NOTE: I had some problems getting tr and sed to properly strip out the special characters; I'm open to suggestions. On the plus side ... I'm not spawning any sub processes.

    We'll store the new format width in variable w2 ('w'idth for field '2') like such:

    w2=$(( 35 + len2 - lenx ))
    

    And the new printf format string becomes:

    printf "%-35s %-${w2}s %-s\n" ...
    

    Pulling it all together we get:

    B="$(tput bold)" # Bold text
    N="$(tput sgr0)" # Normal text
    
    function testing(){
    
    IN="KEYS | VALUES | DESCRIPTION
    id | ${B}10${N} or ${B}20${N} | Enter ID. Default is ${B}10${N}.
    status | ${B}true${N} or ${B}false${N} | Enter status of something. Default is ${B}true${N}.
    style | Example: Standard | Give suitable standard."
    
        IFS= 
        while read -r lines; do
            IFS='|' read -r -a array <<< "$lines"
    
            len2="${#array[1]}"
    
            x="${array[1]//${B}/}"
            x="${x//${N}/}"
            lenx=${#x}
    
            w2=$(( 35 + len2 - lenx ))
       #    echo "w2 = ${w2}"
    
            printf "%-35s %-${w2}s %-s\n" "${array[0]}" "${array[1]}" "${array[2]}"
        done <<< "$IN"
    
        read -p "$*"
        exit 0
    }
    

    Running the script in my bash env generates:

    $ testing
    KEYS                                 VALUES                              DESCRIPTION
    id                                   10 or 20                            Enter ID. Default is 10.
    status                               true or false                       Enter status of something. Default is true.
    style                                Example: Standard                   Give suitable standard.
    

    NOTE: The bold fields do print as bold on my terminal ... they just don't copy/display as bold in the above answer.

    If you uncomment the line - echo "w2 = ${w2}" - you should find (for the 2 lines of interest) that we're going to use a format width of 55 for the second field (ie, the desired 35 plus an extra 20 to compensate for the 20 characters worth of non-printable characters).