Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm looking for a pipeable one-liner to reorder a large number of columns (where manually entering the column numbers in e.g. an awk command such as awk '{print $3,$2,$1}' is not feasible). The order could be given by a sorting scheme (alphabetical, numerical - so like 'sort' but acting on columns rather than rows.) or be arbitrarily given in a text file.

share|improve this question

4 Answers 4

Here is a streamable solution.

I assume you want to sort based on the first row of the columns, otherwise adapt to get the sorting key from somewhere else.

Generate sorting key (reusing Rush's array):

echo -e  "2 1 3\n5 4 6\n8 7 9" > data

key=$(head -n1 data | tr -s ' ' | tr ' ' '\n' | cat -n \
      | sort -k2 | sed 's/^ *\(.*\)\t.*/\1/')

$key now holds:

2
1
3

Now use the key to sort columns:

cat data | awk -v key="$key" '
BEGIN { split(key, order, "\n") }

{ 
  for(i in order) { 
    printf("%s ", $order[i])
  }
  printf("\n");
}'

Output:

1 2 3 
4 5 6 
7 8 9
share|improve this answer

I'm not sure it is the best solution and I'm not sure it will work fast on huge tables, but it should work:

echo -e  "2 1 3\n5 4 6\n8 7 9"  | \
awk '{for (i=1;i<=NF;i++) {a[NR,i]=$i} } \
     NF>p {p=NF} \
     END {for (j=1;j<=p;j++) {str=a[1,j]; \
     for (i=2;i<=NR;i++) {str=str" "a[i,j];}print str}}' \ 
     | sort -n  | \
awk '{for (i=1;i<=NF;i++) {a[NR,i]=$i} } \
     NF>p {p=NF} \
     END {for (j=1;j<=p;j++) {str=a[1,j]; \
     for (i=2;i<=NR;i++) {str=str" "a[i,j];}print str}}'

How it works: it transposes table, then it sorts the table and transposes it back.

btw echo -e "2 1 3\n5 4 6\n8 7 9" will result to

2 1 3
5 4 6
8 7 9

After script work it will result to

1 2 3
4 5 6
7 8 9

ps. I think it is possible to sort array in awk, unfortunately I haven't enough time to do it.

share|improve this answer

If the column numbers are separated by spaces and in a text file (which you say you're ok with), then this one-liner will formulate the awk '{print ...}' stuff.

awk '{print $'$(sed -e 's/ /,$/g' < cols.txt)'}'

Alternatively, if the column numbers are on a separate line in the text file:

awk '{print $'$(echo $(cat cols.txt) | sed -e 's/ /,$/g')'}'
share|improve this answer

Assuming your file is xy.dat, and separated by blank:

cat xy.dat | while read line ; do  
   echo $line | tr ' ' '\n' | sort -nr | tr '\n' ' '
   echo
done

Since my testdata was numeric ascending, I use sort -nr in the heart, to make it descending, and see some effect.

Now to make it configurable, one would just pass as parameters the flags for sort, which allows ascending (none) and descending -r (reverse), but also -n (numerical) and many more (see: sort --help). Another thing you might like configurable is the delimiter. Blank/Tab/Semicolon/Comma? Maybe a regex-group like "[ \t]" to mean blank-or-tab? But what to use for output then? And you wouldn't like to hardcode the filename, but use your program as a filter. Here is a fast approach:

#!/bin/bash
flags=$1
delim=$2 
while read line ; do  
    echo $line | tr "$delim" '\n' | sort $flags | tr '\n' "$delim"
    echo
done

invocation:

asux:~/proj/mini/forum > cat num.dat | bash colsort.sh "-nr" ' ' 
4 3 2 1 
8 7 6 5 
11 10 9 
asux:~/proj/mini/forum > cat num.dat | bash colsort.sh "-r" ' ' 
4 3 2 1 
8 7 6 5 
9 11 10 
asux:~/proj/mini/forum > cat num.dat | bash colsort.sh "--" ' ' 
1 2 3 4 
5 6 7 8 
10 11 9 

See how it sorted by default with -- (alphabetical: 10 11 9), reverse (9 10 11) or numerical (11 10 9).

Todo: A helpscreen, manpage, howto, apropos, video-tutorial. :)

Mainly how to mask blank, tab and so on would be helpful, if documented.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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