85

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?

7 Answers 7

110

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
Sign up to request clarification or add additional context in comments.

6 Comments

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.
@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)..
I know, wrong topic, but: this is usefull for pretty-printing CSS, it helps aligning the properties off css-statements beautifully!
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).
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..
|
57

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}'

1 Comment

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}'
4

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

Comments

4

Try

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

or even

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

if input is not very large.

1 Comment

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.
4

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 }'

Comments

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";

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.