dersimn
dersimn

Reputation: 875

Format columns in string nicely with bash tools

Let's say I have a file orders.txt which contains something like:

#   Description Amount  Price   Sum
1   Beermat 1000    0,01€   10€
2   Glass   100 1€  100€
3   Long description    1   10€ 10€
4   An even longer description  1   10€ 10€
5   An extra long description, for real!    1   10€ 10€
6   An extra long description, almost max. length   1   10€ 10€
7   Long description for some really fancy product and unfortunately this description is too long to fit into one line - bad luck!  1   10€ 10€
8   This line isn’t shown afterwards    1   1€  1€

Where the columns are separated with a tabstop a.k.a. \t

Usually I format these things with a little tool column -ts $'\t' order.txt which results in something like:

#  Description                                    Amount  Price  Sum
1  Beermat                                        1000    0,01€  10€
2  Glass                                          100     1€     100€
3  Long description                               1       10€    10€
4  An even longer description                     1       10€    10€
5  An extra long description, for real!           1       10€    10€
6  An extra long description, almost max. length  1       10€    10€

This works fine as long as one line doesn't exceed the line width of your terminal window. So in case of line #7 this tool outputs a column: line too long and exits.

What I'm looking for is a solution to generate me an output like this one:

#  Description                                    Amount  Price  Sum
1  Beermat                                        1000    0,01€  10€
2  Glass                                          100     1€     100€
3  Long description                               1       10€    10€
4  An even longer description                     1       10€    10€
5  An extra long description, for real!           1       10€    10€
6  An extra long description, almost max. length  1       10€    10€
7  Long description for some really fancy product 1       10€    10€
   and unfortunately this description is too long
   to fit into one line - bad luck!
8  This line isn’t shown afterwards               1       1€     1€

Upvotes: 0

Views: 176

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84561

Well handling the long descriptions and printing the Amount, Price and Sum after a fixed width while splitting the string to print the remainder after the Amount, Price and Sum line is not trivial. There are more than a few ways to split strings, and more ways that are more elegant, but a brute-force example will give you an idea. You can tidy this up as you see fit. Just set your width by changing the dwidth variable or providing the desired width as a second argument after the filename.

This assumes your input format is as you described, with fields separated by tabs for example: #\tDescription\tAmount\tPrice\tSum

#!/bin/bash

test -r "$1" || { 
    printf "Error: insufficient input, usage ${0//*\//} <orders file>\n\n"
    exit 1
}

oifs=$IFS           # set IFS to only break on tab or newline
IFS=$'\t\n'

dwidth=${2:-50}     # set the print width you want for description (default 50)
i=0

while read num desc amt price sum || test -n "$num"; do

    # test description > width, if so print only first 50 (or on word break)
    if test "${#desc}" -ge "$dwidth" ; then
        for ((i=$dwidth; i>0; i--)); do
            test "${desc:$i:1}" = ' ' && break
        done

        end=$i
        printf "%2s %-*s %-8s %-8s %-8s\n" $num $dwidth "${desc:0:end}" $amt $price $sum

        remain=$((${#desc}-$end))       # calculate remaining chars to print

        while test "$remain" -gt 0; do  # while characters remain
            strt=$((end+1))             # start printing at last end
            if test "$remain" -gt "$dwidth"; then   # test if more than width remain
                for ((i=$dwidth; i>0; i--)); do     # if so, break on word
                    test "${desc:$((strt+i)):1}" = ' ' && break
                done
                end=$((strt+i))         # set end equal to start + chars in words
                printf "   %-*s\n" $dwidth "${desc:$strt:$i}"   # print to width
            else
                printf "   %-*s\n" $dwidth "${desc:$strt}"      # print rest and break
                break
            fi
            remain=$((${#desc}-$end))   # calculate new remaining chars
        done
    else    # if description not > width, just print it
        printf "%2s %-*s %-8s %-8s %-8s\n" $num $dwidth $desc $amt $price $sum
    fi

done < "$1"

exit 0

output: $ bash orders.sh orders.txt

# Description                                        Amount   Price    Sum
1 Beermat                                            1000     0,01€    10€
2 Glass                                              100      1€       100€
3 Long description                                   1        10€      10€
4 An even longer description                         1        10€      10€
5 An extra long description, for real!               1        10€      10€
6 An extra long description, almost max. length      1        10€      10€
7 Long description for some really fancy product and 1        10€      10€
  unfortunately this description is too long to fit
  into one line - bad luck!
8 This line isn’t shown afterwards                   1        1€       1€

output: $ bash orders.sh orders.txt 60

# Description                                                  Amount   Price    Sum
1 Beermat                                                      1000     0,01€    10€
2 Glass                                                        100      1€       100€
3 Long description                                             1        10€      10€
4 An even longer description                                   1        10€      10€
5 An extra long description, for real!                         1        10€      10€
6 An extra long description, almost max. length                1        10€      10€
7 Long description for some really fancy product and           1        10€      10€
  unfortunately this description is too long to fit into one
  line - bad luck!
8 This line isn't shown afterwards                             1        1€       1€

Upvotes: 3

Related Questions