62

E.g. I have a file (produced with echo -e "var1\tvar2\t\var3\tvar4" > foo) that are output as:

$ cat foo
case    elems   meshing nlsys
uniform 2350    0.076662        2.78
non-conformal   348     0.013332        0.55
scale   318     0.013333        0.44
smarter 504     0.016666        0.64
submodel        360     .009999 0.40
unstruct-quad   640     0.019999        0.80
unstruct-tri    1484    0.01    0.88

I'd prefer the output like this (here I used vim and :set tabstop=14):

case          elems         meshing       nlsys
uniform       2350          0.076662      2.78
non-conformal 348           0.013332      0.55
scale         318           0.013333      0.44
smarter       504           0.016666      0.64
submodel      360           .009999       0.40
unstruct-quad 640           0.019999      0.80
unstruct-tri  1484          0.01          0.88

I can get the same functionality with cat if I use $ tabs=15 in bash (see this question). Is there a program that does this kind of formatting automatically? I don't want to experiment with the tabs value before cating a file.

81

I usually use the column program for this, it's in a package called bsdmainutils on Debian:

column -t foo

Output:

case           elems  meshing   nlsys
uniform        2350   0.076662  2.78
non-conformal  348    0.013332  0.55
scale          318    0.013333  0.44
smarter        504    0.016666  0.64
submodel       360    .009999   0.40
unstruct-quad  640    0.019999  0.80
unstruct-tri   1484   0.01      0.88

Excerpt from column(1) on my system:

...

-t      Determine the number of columns the input contains and create a
        table.  Columns are delimited with whitespace, by default, or
        with the characters supplied using the -s option.  Useful for
        pretty-printing displays.

...
  • great! thanks a lot! It was already installed on my machine. – Sebastian Sep 11 '12 at 14:39
  • 10
    you may want to add the -s $'\t' (not found in every column implementations though) in case some of the fields contain spaces. – Stéphane Chazelas Sep 11 '12 at 15:07
  • 2
    @RakholiyaJenish $'\t' means tab character. New line is $'\n' and so on. – Manwe Sep 24 '15 at 10:31
  • 1
    I used this as column -ts: /etc/passwd. Looks cool! – kyb Sep 1 '18 at 16:56
  • 1
    @kyb: looks even better with -n, i.e. avoid merging multiple adjacent delimiters – Thor Sep 4 '18 at 10:19
10

Several options:

var1=uniform var2=2350 var3=0.076662 var4=2.78

printf '%-15s %-10s %-12s %s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4"

printf '%s\t%s\t%s\t%s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4" |
  expand -t 15,25,37

printf '%s\t%s\t%s\t%s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4" |
  column -t -s $'\t'

column is a non-standard command, some implementations/versions don't support the -s option. It computes the width of the column based on the input, but that means that it can only start displaying once all the input has been fed to it. $'...' is ksh93 syntax also found in zsh and bash.

With zsh:

values=(
  case elems messing nlsys
  "$var1" "$var2" "$var3" "$var4"
)
print -arC4 -- "$values[@]"
4

You can also use rs as an alternative to column -t:

(x=$(cat);rs -c -z $(wc -l<<<"$x")<<<"$x")

-c changes the input column separator, but -c alone sets the input column separator to a tab. -z sets the width of each column to the width of the longest entry of the column instead of making all columns the same width. If some lines have fewer columns than the first line, add -n.

  • Which rs is that? I haven't got that command installed on my CentOS nor my Ubuntu/Mint systems. – Anthon Mar 5 '16 at 12:44
  • 1
    @Anthon It is a BSD command that also comes with OS X, named after the reshape function in APL. The Debian package name is just rs, so you can install it with apt-get install rs. – nisetama Mar 6 '16 at 10:50
3

Another tool that can do this is tsv-pretty from eBay's TSV Utilities (disclaimer: I'm the author). It takes the additional step of lining up numeric fields on the decimal point. For example:

$ tsv-pretty foo
case           elems   meshing  nlsys
uniform         2350  0.076662   2.78
non-conformal    348  0.013332   0.55
scale            318  0.013333   0.44
smarter          504  0.016666   0.64
submodel         360   .009999   0.40
unstruct-quad    640  0.019999   0.80
unstruct-tri    1484  0.01       0.88

There are several formatting options. For example, -u underlines the header and -f formats the floats in a field similarly for readability:

$ tsv-pretty foo -f -u
case           elems   meshing  nlsys
----           -----   -------  -----
uniform         2350  0.076662   2.78
non-conformal    348  0.013332   0.55
scale            318  0.013333   0.44
smarter          504  0.016666   0.64
submodel         360  0.009999   0.40
unstruct-quad    640  0.019999   0.80
unstruct-tri    1484  0.010000   0.88

More info is available in the tsv-pretty reference.

  • This is really helpful – Arefe Oct 26 '18 at 7:05
1

The question was about outputting tab delimited columns.

So the correct answer is a small adaptation of the answer of @nisetama. I added the -C$'\t' option which sets output formatting.

x=$(cat foo2); rs -C$'\t' $(wc -l <<<"$x") <<<"$x"

Kudo's to @nisetama though :)

1
function printTable()
{
    local -r delimiter="${1}"
    local -r data="$(removeEmptyLines "${2}")"

    if [[ "${delimiter}" != '' && "$(isEmptyString "${data}")" = 'false' ]]
    then
        local -r numberOfLines="$(wc -l <<< "${data}")"

        if [[ "${numberOfLines}" -gt '0' ]]
        then
            local table=''
            local i=1

            for ((i = 1; i <= "${numberOfLines}"; i = i + 1))
            do
                local line=''
                line="$(sed "${i}q;d" <<< "${data}")"

                local numberOfColumns='0'
                numberOfColumns="$(awk -F "${delimiter}" '{print NF}' <<< "${line}")"

                # Add Line Delimiter

                if [[ "${i}" -eq '1' ]]
                then
                    table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                fi

                # Add Header Or Body

                table="${table}\n"

                local j=1

                for ((j = 1; j <= "${numberOfColumns}"; j = j + 1))
                do
                    table="${table}$(printf '#| %s' "$(cut -d "${delimiter}" -f "${j}" <<< "${line}")")"
                done

                table="${table}#|\n"

                # Add Line Delimiter

                if [[ "${i}" -eq '1' ]] || [[ "${numberOfLines}" -gt '1' && "${i}" -eq "${numberOfLines}" ]]
                then
                    table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                fi
            done

            if [[ "$(isEmptyString "${table}")" = 'false' ]]
            then
                echo -e "${table}" | column -s '#' -t | awk '/^\+/{gsub(" ", "-", $0)}1'
            fi
        fi
    fi
}

function removeEmptyLines()
{
    local -r content="${1}"

    echo -e "${content}" | sed '/^\s*$/d'
}

function repeatString()
{
    local -r string="${1}"
    local -r numberToRepeat="${2}"

    if [[ "${string}" != '' && "${numberToRepeat}" =~ ^[1-9][0-9]*$ ]]
    then
        local -r result="$(printf "%${numberToRepeat}s")"
        echo -e "${result// /${string}}"
    fi
}

function isEmptyString()
{
    local -r string="${1}"

    if [[ "$(trimString "${string}")" = '' ]]
    then
        echo 'true' && return 0
    fi

    echo 'false' && return 1
}

function trimString()
{
    local -r string="${1}"

    sed 's,^[[:blank:]]*,,' <<< "${string}" | sed 's,[[:blank:]]*$,,'
}

SAMPLE RUNS

$ cat data-1.txt
HEADER 1,HEADER 2,HEADER 3

$ printTable ',' "$(cat data-1.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+

$ cat data-2.txt
HEADER 1,HEADER 2,HEADER 3
data 1,data 2,data 3

$ printTable ',' "$(cat data-2.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+
| data 1    | data 2    | data 3    |
+-----------+-----------+-----------+

$ cat data-3.txt
HEADER 1,HEADER 2,HEADER 3
data 1,data 2,data 3
data 4,data 5,data 6

$ printTable ',' "$(cat data-3.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+
| data 1    | data 2    | data 3    |
| data 4    | data 5    | data 6    |
+-----------+-----------+-----------+

$ cat data-4.txt
HEADER
data

$ printTable ',' "$(cat data-4.txt)"
+---------+
| HEADER  |
+---------+
| data    |
+---------+

$ cat data-5.txt
HEADER

data 1

data 2

$ printTable ',' "$(cat data-5.txt)"
+---------+
| HEADER  |
+---------+
| data 1  |
| data 2  |
+---------+

REF LIB at: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash

  • interesting bash-only solution -- thanks for sharing – Sebastian Mar 10 '18 at 4:56
  • This is too convoluted. And it is not bash-only since there are external commands like sed being used. – codeforester Apr 13 '18 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.