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 have a file with columns. See below for an example:

a b c ... z  
1 2 3 ... 26

I'd like to swap all columns where the 1st becomes the last, the second becomes the one before last...etc..

z y x ... a  
26 25 24 ... 1

Is there a one liner (awk or sed) that does this?
I know one can use awk when there is only a couple columns, but I'd like to be able to do this on files with thousands of columns.

tac does this perfectly for lines.
I guess I am looking for the equivalent for columns.

rev hasn't worked for me, as it also swaps content in the column.

share|improve this question
add comment

7 Answers 7

awk '{for(i=NF;i>0;i--)printf "%s ",$i;print ""}' file
share|improve this answer
    
I did too hard work for such a simple task. Always simpler it's better. +1 –  Birei Aug 24 '12 at 20:39
    
Thanks! This works fine. –  user22519 Aug 24 '12 at 20:53
add comment

You could do it with a small python script:

#!/usr/bin/env python

# Swaps order of columns in file, writes result to a file.
# usage: program.py input_file output_file

import sys, os

out = []

for line in open(sys.argv[1], 'r'):
    fields = line.split()
    rev = ' '.join(list(reversed(fields)))
    out.append(rev)

f = open(sys.argv[2], 'w')
f.write(os.linesep.join(out))
share|improve this answer
add comment

If you don't mind python then this one-liner will reverse the order of space separated columns in every line:

paddy$ cat infile.txt 
a b c d e f g h i j k l
1 2 3 4 5 6 7 8 9 10 11 12
a e i o u
paddy$ python3 -c 'with open("infile.txt") as f: print("\n".join(" ".join(line.rstrip().split()[::-1]) for line in f))'
l k j i h g f e d c b a
12 11 10 9 8 7 6 5 4 3 2 1
u o i e a
paddy$ 

The above works with python2.7 too:

paddy$ python2.7 -c 'with open("infile.txt") as f: print("\n".join(" ".join(line.rstrip().split()[::-1]) for line in f))'
l k j i h g f e d c b a
12 11 10 9 8 7 6 5 4 3 2 1
u o i e a
paddy$ 
share|improve this answer
    
This method is the fastest of all the ansers I've tested. –  Peter.O Aug 26 '12 at 23:25
add comment

One way using awk.

Content of infile:

a b c d e f g h i j k l
1 2 3 4 5 6 7 8 9 10 11 12
a e i o u

Run following awk command:

awk '{
    ## Variable 'i' will be incremented from first field, variable 'j'
    ## will be decremented from last field. And their values will be exchanged.
    ## The loop will end when both values cross themselves.
    j = NF; 
    for ( i = 1; i <= NF; i++ ) { 
        if ( j - i < 1 ) { 
            break;
        } 
        temp = $j; 
        $j = $i; 
        $i = temp; 
        j--; 
    }
    print;
}' infile

With following result:

l k j i h g f e d c b a
12 11 10 9 8 7 6 5 4 3 2 1
u o i e a
share|improve this answer
add comment

This is slow, but it does have one redeeming feature. It maintains the width of field separators, when they are wider than a single character. FWIW: If you run this script twice, the result is identical to the original.

Here is the script.

awk '{ eix = length($0) 
       for( fn=NF; fn>0; fn--) { dix=eix
            while( substr($0,dix,1) ~ /[ \t]/ ) dix--
            printf "%s%s", substr($0,dix+1,eix-dix), $fn
            dix-=length($fn); eix=dix }
       print substr($0,1,dix)
    }' "$file"

Here are some time comparisons. The test file contained 1 line.

                      fields           fields     
                      10,0000          10,000,000

user11136 {python} | real  0.029s     real  3.235s
reversible? no     | user  0.032s     user  2.008s
                   | sys   0.000s     sys   1.228s

jmp {python}       | real  0.078s     real  5.045s
reversible? no     | user  0.068s     user  4.268s
                   | sys   0.012s     sys   0.560s

rush {awk}         | real  0.120s     real  10.889s
reversible? no     | user  0.116s     user   8.641s
                   | sys   0.008s     sys    2.252s

petero {awk}       | real  0.319s     real  35.750s
reversible? yes    | user  0.304s     user  33.090s
                   | sys   0.016s     sys    2.660s
share|improve this answer
add comment

You can use tac you just need to transpose the input before and after. This can be done with the spreadsheet calculator sc and its sidekick psc:

< infile psc -S -r | sc -W% - | tac | psc -S -r | sc -W% - > outfile

As seen here.

This works best when all the columns are filled.

infile

 a b c d e f g h i  j  k  l
 1 2 3 4 5 6 7 8 9 10 11 12
 A B C D E F G H I  J  K  L

outfile

  l  k  j i h g f e d c b a
 12 11 10 9 8 7 6 5 4 3 2 1
  L  K  J I H G F E D C B A

Edit

As noted by PeterO sc has a hard limit of 702 columns, so that is the maximum size supported by this method.

share|improve this answer
1  
It converts numbers to floating points (for me) eg. 1 -> 1.00. Also, I get errors for lines more than 702 fields wide. It seem to be relate to a numeric limit of 32768... but it is quite fast, asis. –  Peter.O Aug 27 '12 at 1:55
    
I do not see the floating point conversion, but adding -S to the psc command should interpret everything as strings. With regards to the 702 column limit, that's a hard limit because only A to ZZ columns are supported (26 + 26 * 26), I'll add a comment about that. –  Thor Aug 27 '12 at 9:53
1  
Actually, the floating point issue is ok.I looked further into it, and I discovered that I shouldn't check results as I'm hurrying out the door.. The floating points only occur after it hits the 702 limit... It is faster than the python answers for 1 line of 702 fields, but for 100 lines it becomes the slowest of all the given methods :( .. It must have a shorter start-up time than python. –  Peter.O Aug 27 '12 at 10:20
add comment

This pipeline is faster than the fastest other answer by a significant factor (see results). It uses tr and tac. It does need to utilize 2 ASCII bytes (\x00-\x7F) which don't exist in your data.

\x00 is typically a good choice, as is \x01, but you can use any ASCII byte which is not in the data.

In this example, SPACE and TAB as the delimiters characters. Delimiters can be multi-byte or single. The output delimiter is a single space.

Here is the command. The filename shows the numberof fields _x number of lines

 <"$file" tr ' \t\n' '\0\0\1' |tr -s '\0' '\n' |tac |tr '\n' ' ' |tr '\1' '\n'

If you want/need to check for the unused bytes, you can check beforehand with this optional awk script. The overall time, even when running this optional script, is still significantly faster than other metods (so far :).. Here is the pre-processing script.

o=($(<"$file" char-ascii-not-in-stream)); x="${o[0]}"; y="${o[1]}"
<"$file" tr ' \t\n' "$x$x$y" |tr -s "$x" '\n' |tac |tr '\n' ' ' | tr '$y' '\n' >"$file".$user

This is the awk script: char-ascii-not-in-stream

#!/usr/bin/awk -f
{c[$0]} END{for(i=0;i<=127;i++) {if(sprintf("%c", i) in c);else {printf "\\%03o ",i}}}

The second set of times, for this script, include char-ascii-not-in-stream's time.

Peter.O {tr,tac,tr} ==== file_10_x10000
real    0m0.013s    0m0.015s
user    0m0.020s    0m0.020s
sys     0m0.008s    0m0.012s   

user11136 {python} ===== file_10_x10000
real    0m0.057s
user    0m0.048s
sys     0m0.008s

jmp {python} =========== file_10_x10000
real    0m0.160s
user    0m0.160s
sys     0m0.000s

rush {awk} ============= file_10_x10000
real    0m0.121s
user    0m0.120s
sys     0m0.000s

##############################################

Peter.O {tr,tac,tr} ==== file_1000_x1000
real    0m0.048s    0m0.059s
user    0m0.040s    0m0.040s
sys     0m0.040s    0m0.048s

user11136 {python} ===== file_1000_x1000
real    0m0.158s
user    0m0.136s
sys     0m0.028s

jmp {python} =========== file_1000_x1000
real    0m0.327s
user    0m0.320s
sys     0m0.008s

rush {awk} ============= file_1000_x1000
real    0m0.832s
user    0m0.820s
sys     0m0s012s

##############################################

Peter.O {tr,tac,tr} ==== file_1000000_x50
real    0m5.221s    0m6.458s
user    0m4.208s    0m5.248s
sys     0m2.624s    0m2.396s

user11136 {python} ===== file_1000000_x50
real    0m16.286s
user    0m10.041s
sys     0m5.148s

jmp {python} =========== file_1000000_x50
real    0m22.845s
user    0m20.705s
sys     0m1.140s

rush {awk} ============= file_1000000_x50
real    0m44.793s
user    0m43.583s
sys     0m0.848s

##############################################
share|improve this answer
add comment

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.