Transpose whole file and specific columns

Hi,

I have a file like this

a b c
d e f
g h i
j k l

Case1:
I want to transpose the whole file

Output1

a d g j
b e h k
c f i l

Case2
Transpose a specific column - Say 3rd

Output2

c f i l

Case3
Transpose a range of columns - say 1 and 2

Output3

a d g j
b e h k

Case4
Transpose specific multiple columns - say 1 and 3

Output

a d g j
c f i l

Initally, I was helped in this forum for transposing a single column of this kind

input

a
b
c
d

using

tr '\n' ',' < input.txt

Output

a,b,c,d
#!/bin/bash 
# 
# transpose 
 
transpose() 
{ 
  awk ' 
      { 
              if(max_nf<NF) 
                 max_nf=NF 
              max_nr=NR 
              for(x=1; x<=NF; ++x) 
                  matrix[x, NR]=$x 
      } 
 
  END { 
              for(x=1; x<=max_nf; ++x) { 
                  for (y=1; y<=max_nr; ++y) 
                       printf("%s ", matrix[x, y]) 
                  printf("\n") 
              } 
      }'  ${1} 
} 
 
transpose ${1} 

for case1:

bash transpose infile

for any other cases:

awk '{print $1,$2,...,$n}' infile | bash transpose 
2 Likes

you may try below, let's see it is transpose.sh

transpose the whole file
!>transpose.sh file

transpose the file with column range 1 to 3
!>transpose.sh file 1,3

transpose the file with column list 1, 3 and 5
!>transpose.sh file 1 3 5

file=$1
shift
function transpose()
{
    tmp_file=$1
awk '{
    for(i=1;i<=NF;i++)
      _=sprintf("%s %s",_,$i)
}
END{
    for(i in _)
        print _
}' $tmp_file
}

if [ $# -eq 0 ];then
echo "beginning to transpose the whole file..."
transpose $file
elif [ $# -eq 1 ];then
range=$1
echo "beginning to transpose range $range ..."
cmd='cut -d" "  -f'${range}' '${file}'>'${file}'.tmp'
eval $cmd
transpose ${file}.tmp
rm ${file}.tmp
elif [ $# -gt 1 ];then
while [ $# -gt 0 ];do
if [ $list ];then
list=${list}","$1
else
list=$1
fi
shift
done
echo "beginning to transpose list of columns $list ..."
cmd='cut -d" "  -f'${list}' '${file}'>'${file}'.tmp'
eval $cmd
transpose ${file}.tmp
rm ${file}.tmp
fi
1 Like

Thanks for ur time. No output is being generated.

---------- Post updated at 02:57 PM ---------- Previous update was at 02:55 PM ----------

$ cat test
a b c
d e f
g h i
j k l

$ ./code.sh test
beginning to transpose the whole file...
 b e h k
 c f i l
 a d g j

$ ./code.sh test 1,2
beginning to transpose range 1,2 ...
 b e h k
 a d g j

$ ./code.sh test 1 3
beginning to transpose list of columns 1,3 ...
 c f i l
 a d g j

$ ./code.sh test 2 3
beginning to transpose list of columns 2,3 ...
 c f i l
 b e h k

The only problem is the shift in order. Thanks a lot. It works great.

[root@node3 ~]# cat infile 
a b c
d e f
g h i
j k l

case1:

[root@node3 ~]# bash transpose infile 
a d g j 
b e h k 
c f i l 

case2:

[root@node3 ~]# awk '{print $3}' infile | bash transpose 
c f i l 

case3:

[root@node3 ~]# awk '{print $1,$2}' infile | bash transpose 
a d g j 
b e h k

case4:

 
[root@node3 ~]# awk '{print $1,$3}' infile | bash transpose 
a d g j 
c f i l 

Don't be so rigid, for dealing with technology issues, we must be more flexible!

2 Likes

That's a poor job of parameter handling. Without double-quoting, the script cannot handle filenames containing whitespace or pathname pattern-matching metacharacters (*, ?, [...]).

You can double-quote the parameter, but, if the script isn't provided an argument, $1 will expand to a zero-length string instead of no field at all. There's a difference between the two and your script depends on this difference to default to reading stdin.

Since AWK is required to support - as an explicit reference to standard input, it makes sense to use it as a default value when $1 is unset (assuring that the script can function as a filter in a pipeline). "${1--}" will do this and will properly handle whitespace and pattern matching characters in a pathname. It would also be a good idea to exit with an error if the filename argument is provided but null.

This script suffers from the same problems as the other, but the damage is exacerbated by the incorrect use of eval .

In addition to whitespace, your eval approach is vulnerable to filenames with all kinds of shell metacharacters (to name a few): ;&*?"{([' . Aside from the possiblity of unintentionally running a command (possibly in the background), whitespace will cause field splitting to pass a partial, incorrect filename to cut and to the redirection operator.

Worst of all is that these bugs are the result of unnecessary complexity. There's nothing about ...

cmd='cut -d" "  -f'${range}' '${file}'>'${file}'.tmp'

... that requires eval ; nothing at all. Just run it directly:

cut -d ' ' -f "${range}" "${file}" > "${file}.tmp"

Regards,
Alister

Thanks. It works now. Donno what happened earlier. Sorry.