Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash: Wrap Long Lines Inside the Same Column

I would like to create a table with two columns. The first column contains the key name and the second column contains the value. The value can be text that usually results in multiple lines in the terminal.

With printf or column I can easily get the following output:

<----- Terminal Length ------>
key1     This is the value for
key1 with a very long text. 
...

But I would like the value to be displayed in the same column like this:

<----- Terminal Length ------>
key1     This is the value for
         key1 with a very long
         text.
...

How can I wrap a long line inside the same column?

like image 206
phobic Avatar asked May 02 '16 10:05

phobic


3 Answers

Solution based on andlrc's comment:

columnize2 () { 
    indent=$1; 
    collen=$(($(tput cols)-indent)); 
    keyname="$2"; 
    value=$3; 
    while [ -n "$value" ] ; do 
        printf "%-10s %-${indent}s\n" "$keyname" "${value:0:$collen}";  
        keyname="";
        value=${value:$collen}; 
    done
}

longvalue=---------------------------------------------------------------------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
columnize2 30 key1 $longvalue
key1       --------------------------------------------------
           -------------------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
           xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzz

tput cols returns the amount of characters per line in the terminal window. We use this to determine how many characters of the value we can print per line (collen). If it does not fit on one line, the rest is printed on the following lines. %-10s in the print statement is used to allocate 10 characters to display the keyname (long keys are not handled well). %-${indent}s is used to indent the value by #indent characters. Only print as many characters of the value as will fit on one line:${value:0:$collen} and strip the already printed characters from the value value=${value:$collen} For following lines we don't print the keyname (by setting it to an empty string).

like image 78
phobic Avatar answered Nov 15 '22 23:11

phobic


Actually, the util-linux 'column' command can do it. It is very versatile.

#!/bin/bash

cat <<- EOF | column --separator '|' \
                     --table \
                     --output-width 30 \
                     --table-noheadings \
                     --table-columns C1,C2 \
                     --table-wrap C2
key1|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
key2|blahhhhhhhhhhhhhhhhhhhhhhhhhhhhzzz
EOF

This gives :

key1  Lorem ipsum dolor sit am
      et, consectetur adipisci
      ng elit, sed do eiusmod
      tempor incididunt ut lab
      ore et dolore magna aliq
      ua.
key2  blahhhhhhhhhhhhhhhhhhhhh
      hhhhhhhzzz

--output-width : give desired size (can use 'tput cols' as described above)

--table-columns C1,C2 : give names to columns to be used with other options

--table-wrap C2 : wrap column 2

Column version :

# column -V
column from util-linux 2.33.1
like image 21
Olivier Delrieu Avatar answered Nov 15 '22 23:11

Olivier Delrieu


I was wondering the same question recently. I wrote this small script called printf-wrap to achieve this feature, inspired by the top answer by @phobic. The reason why it is necessary is that the version of column on a user's workstaion may not have wrapping capability and he/she may not be authorized to install or update any packages either. (Yes, the user is me ...)

printf-wrap accepts exactly the same input arguments as printf, and wrap smartly the text within every columns. And you can use tput to adjust it to terminal width.

# printf-wrap
# Description:  Printf with smart word wrapping for every columns.
# Usage:        print-wrap "<format>" "<text>" "<text>" ...
function printf-wrap {
    # help info
    help_message="Usage: print-column-wrap \"<format>\" \"<text>\" \"<text>\" ..."
    if [[ "$1" =~ ^(-|--)?(h|help)$ ]] ; then echo "$help_message" ; return 0 ; fi 
    # parse argument
    format="$1"
    width=( $( echo "$format" | sed -r 's/%%|\\%//g' | sed -r 's/%/\n%/g' | sed -n -r 's/^%[^1-9]*([0-9]+).*/\1/p' ) )
    shift
    text=( "$@" )
    error_message="Error: number of input text fields (${#text[@]}) MUST NOT EXCEED number of columns specified (${#width[@]}) !"
    if (( ${#text[@]} > ${#width[@]} )) ; then echo "$error_message" ; return 1 ; fi
    # printing
    while [ -n "$( echo "${text[@]}" | sed -r 's/\s+//g' )" ] ; do
        text_cut=()
        width_cut=()
        for i in ${!text[@]} ; do
            text[$i]="$( echo "${text[$i]}" | sed -r 's/^\s+//' )"
            if [[ "${text[$i]:${width[$i]}-1:2}" =~ [a-zA-Z0-9_]{2} ]] ; then
                text_cut[$i]="$( echo "${text[$i]:0:${width[$i]}}" | sed -r 's/\w+$//' )"
            else
                text_cut[$i]="${text[$i]:0:${width[$i]}}"
            fi
                width_cut[$i]=${#text_cut[$i]}
                text[$i]="${text[$i]:${width_cut[$i]}}"
        done
        printf "$format" "${text_cut[@]}"
    done
}

The ouput looks like the following.

$ printf-wrap "%-10s %-$(( $(tput cols) - 11 ))s\n" "key1" "This is the value for key1 with a very long text"
============ Terminal Width ============
key1       This is the value for key1   
           with a very long text

Hope this can help anyone. :)

like image 1
Boyang Xu Avatar answered Nov 15 '22 23:11

Boyang Xu