How can I format the output of a bash command in neat columns
Asked Answered
A

7

78

I have a function which outputs many rows of information which I want to format in columns. The problem is that the width of any particular "cell" (if I may use that term) of data is variable, so piping it to something like awk does not give me what I want.

The function is "keys" (not that it matters) and I'm trying something like this:

$ keys | awk '{ print $1"\t\t" $2 }'

but the output (a snippet of it, that is) looks like this:

"option-y"      yank-pop
"option-z"      execute-last-named-cmd
"option-|"      vi-goto-column
"option-~"      _bash_complete-word
"option-control-?"      backward-kill-word
"control-_"     undo
"control-?"     backward-delete-char

How can I force things to stay in neat columns? Is this possible with awk, or do I need to use something else?

Applicable answered 24/6, 2011 at 3:7 Comment(0)
T
103

column(1) is your friend.

$ column -t <<< '"option-y"      yank-pop
> "option-z"      execute-last-named-cmd
> "option-|"      vi-goto-column
> "option-~"      _bash_complete-word
> "option-control-?"      backward-kill-word
> "control-_"     undo
> "control-?"     backward-delete-char
> '
"option-y"          yank-pop
"option-z"          execute-last-named-cmd
"option-|"          vi-goto-column
"option-~"          _bash_complete-word
"option-control-?"  backward-kill-word
"control-_"         undo
"control-?"         backward-delete-char
Tavarez answered 24/6, 2011 at 3:11 Comment(6)
nice and fast and useful. but the awk version more easily lets me tweak it to solve another problem: splitting on something other than spaces. I tried with this: IFS='" ' ; keys | column -t but column doesn't seem to respect the value of $IFS.Applicable
@Brandon: IFS is the Internal Field Seperator for the shell, not for programs which are run via the shell, although they may utilize the same value(s)..Muliebrity
I know, wrong topic, but: this is usefull for pretty-printing CSS, it helps aligning the properties off css-statements beautifully!Vibrate
Warning: column does not like lines that are "too long", and while man column mentions a limitation at 2048 bytes, in practice a couple hundred bytes is enough to choke it (on Debian 8.3 at least).Trichina
I can do this on the command line but how would you do the same thing within a function of a script? IE: carriage return when you hit after after 'yank-pop' and subsequent new lines..Tallinn
Another *nix utility to add to my toolbag.Amoeboid
S
53

Found this by searching for "linux output formatted columns": Formatting output in columns

For your needs, it's like:

awk '{ printf "%-20s %-40s\n", $1, $2}'
Stevenson answered 24/6, 2011 at 3:11 Comment(1)
I didn't show this in the sample output I gave, but in some places I have spaces that I don't want to split on, so with this minor tweak to your awk snippet I've solved the problem: keys | awk 'BEGIN { FS = "\" " } ; { printf "%-20s %-40s\n", $1, $2, $3}'Applicable
F
4

While awk's printf can be used, you may want to look into pr or (on BSDish systems) rs for formatting.

Fumy answered 24/6, 2011 at 3:13 Comment(0)
H
3

Since AIX doesn't have a "column" command, I created the simplistic script below. It would be even shorter without the doc & input edits... :)

#!/usr/bin/perl
#       column.pl: convert STDIN to multiple columns on STDOUT
#       Usage: column.pl column-width number-of-columns  file...
#
$width = shift;
($width ne '') or die "must give column-width and number-of-columns\n";
$columns = shift;
($columns ne '') or die "must give number-of-columns\n";
($x = $width) =~ s/[^0-9]//g;
($x eq $width) or die "invalid column-width: $width\n";
($x = $columns) =~ s/[^0-9]//g;
($x eq $columns) or die "invalid number-of-columns: $columns\n";

$w = $width * -1; $c = $columns;
while (<>) {
        chomp;
        if ( $c-- > 1 ) {
                printf "%${w}s", $_;
                next;
        }
        $c = $columns;
        printf "%${w}s\n", $_;
}
print "\n";
Hagler answered 6/11, 2013 at 15:25 Comment(0)
E
3

Try

xargs -n2  printf "%-20s%s\n"

or even

xargs printf "%-20s%s\n"

if input is not very large.

Escalator answered 22/5, 2014 at 18:6 Comment(1)
For those interested, for 4-column data, that would be something like xargs printf "%-5s%-5s%-5s%-5s\n" for the same total width of 20.Improvisation
B
3

If your output is delimited by tabs a quick solution would be to use the tabs command to adjust the size of your tabs.

tabs 20
keys | awk '{ print $1"\t\t" $2 }'
Braise answered 11/2, 2016 at 10:37 Comment(0)
M
0

You can try a function ie. in this case the following breaks up the declared arrays into two columns. $1 is then passed to this script as an arbitrary column width ie. 20. The function spaces () can then be used anywhere additional columns are required.

#!/bin/bash

WI="${1}"
# function
spaces () {
        for (( a=0; a<(( $2 - $1 )); a++ )); do echo -n " "; done
}
# logic
declare -a NUMBERS=(One Two Three Four Five Six Seven)
declare -a DAYS=(Monday Tue Wednesday Thursday Friday Sat Sun)
for i in {0..6}
do
echo "    ${NUMBERS[$i]}$(spaces ${#NUMBERS[$i]} ${WI})${DAYS[$i]}"
done

In your case you could use mapfile to populate the array MAPFILE presuming the file keys contains the un-formatted text. And then iterate through the array using spaces() to format the columns to the correct width.

#!/bin/bash

# function
spaces () {
        for (( a=0; a<(( $2 - $1 )); a++ )); do echo -n " "; done
}
 
# logic
mapfile -t < keys
for line in "${MAPFILE[@]}"; do
    LINE1=$(echo $line | awk '{print $1}')
    LINE2=$(echo $line | awk '{print $2}')
    echo " ${LINE1}$(spaces ${#LINE1} $1)${LINE2}"
done

exit
Mouthful answered 26/11, 2022 at 0:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.