129 votes

Transposer un fichier en bash

J'ai un énorme séparé par des tabulations fichier formaté comme ceci

X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

Je tiens à transposer de manière efficace en utilisant uniquement des commandes bash (je pourrais écrire une dizaine de lignes de script Perl pour le faire, mais il devrait être plus lente à exécuter que le natif de bash fonctions). Si la sortie doit ressembler à

X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11

J'ai pensé à une solution comme ceci

cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done

Mais c'est lent et ne semble pas la solution la plus efficace. J'ai vu une solution pour la vi dans ce post, mais c'est encore plus lent. Des idées/suggestions/idées brillantes? :-)

130voto

ghostdog74 Points 86060

rester bouche bée

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

sortie

 $ more file
0 1 2
3 4 5
6 7 8
9 10 11

$ ./shell.sh
0 3 6 9
1 4 7 10
2 5 8 11
 

Performance par Jonathan de la solution Perl sur un fichier de 10000 lignes

 $ head -5 file
1 0 1 2
2 3 4 5
3 6 7 8
4 9 10 11
1 0 1 2

$  wc -l < file
10000

$ time perl test.pl file >/dev/null

real    0m0.480s
user    0m0.442s
sys     0m0.026s

$ time awk -f test.awk file >/dev/null

real    0m0.382s
user    0m0.367s
sys     0m0.011s

$ time perl test.pl file >/dev/null

real    0m0.481s
user    0m0.431s
sys     0m0.022s

$ time awk -f test.awk file >/dev/null

real    0m0.390s
user    0m0.370s
sys     0m0.010s
 

32voto

Stephan202 Points 27707

Une solution Python:

 python -c "import sys; print('\n'.join(' '.join(c) for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip()))))" < input > output
 

Ce qui précède est basé sur les éléments suivants:

 import sys

for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip())):
    print(' '.join(c))
 

Ce code suppose que chaque ligne a le même nombre de colonnes (aucun remplissage n'est effectué).

22voto

flying sheep Points 1989

le projet de transposition sur sourceforge est un programme C similaire à Coreutil pour cela.

 gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
 

17voto

fgm Points 5930

Pure BASH, pas de processus supplémentaire. Un bel exercice:

 declare -a array=( )                      # we build a 1-D-array

read -a line < "$1"                       # read the headline

COLS=${#line[@]}                          # save number of columns

index=0
while read -a line ; do
    for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
    	array[$index]=${line[$COUNTER]}
    	((index++))
    done
done < "$1"

for (( ROW = 0; ROW < COLS; ROW++ )); do
  for (( COUNTER = ROW; COUNTER < ${#array[@]}; COUNTER += COLS )); do
    printf "%s\t" ${array[$COUNTER]}
  done
  printf "\n" 
done
 

9voto

Jonathan Leffler Points 299946

Ici est moyennement solide script Perl pour faire le travail. Il existe de nombreuses analogies structurelles avec @ghostdog74 de l' awk de la solution.

#!/bin/perl -w
#
# SO 1729824

use strict;

my(%data);          # main storage
my($maxcol) = 0;
my($rownum) = 0;
while (<>)
{
    my(@row) = split /\s+/;
    my($colnum) = 0;
    foreach my $val (@row)
    {
        $data{$rownum}{$colnum++} = $val;
    }
    $rownum++;
    $maxcol = $colnum if $colnum > $maxcol;
}

my $maxrow = $rownum;
for (my $col = 0; $col < $maxcol; $col++)
{
    for (my $row = 0; $row < $maxrow; $row++)
    {
        printf "%s%s", ($row == 0) ? "" : "\t",
                defined $data{$row}{$col} ? $data{$row}{$col} : "";
    }
    print "\n";
}

Avec l'exemple de la taille des données, la différence de performances entre perl et awk est négligeable (1 milliseconde de 7 au total). Avec un plus grand ensemble de données (100x100 matrice, les entrées de 6 à 8 caractères chacune), perl légèrement surperformé awk - 0.026 s vs 0.042 s. Ni est susceptible d'être un problème.


Représentant les horaires pour Perl 5.10.1 (32 bits) vs awk (version 20040207 quand '-V') vs gawk 3.1.7 (32 bits) sur MacOS X 10.5.8 sur un fichier contenant 10 000 lignes avec 5 colonnes par ligne:

Osiris JL: time gawk -f tr.awk xxx  > /dev/null

real    0m0.367s
user    0m0.279s
sys 0m0.085s
Osiris JL: time perl -f transpose.pl xxx > /dev/null

real    0m0.138s
user    0m0.128s
sys 0m0.008s
Osiris JL: time awk -f tr.awk xxx  > /dev/null

real    0m1.891s
user    0m0.924s
sys 0m0.961s
Osiris-2 JL:

Notez que gawk est considérablement plus rapide que awk sur cette machine, mais encore plus lente que perl. Clairement, votre kilométrage peut varier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X