J'ai un énorme fichier séparé par des tabulations formaté comme ça
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
Je voudrais le transposer de manière efficace en n'utilisant que des commandes bash (je pourrais écrire un script Perl d'une dizaine de lignes pour cela, mais il devrait être plus lent à exécuter que les fonctions bash natives). Ainsi, la sortie devrait 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 celle-ci
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 vi dans cet article , mais elle est toujours trop lente. Des pensées / suggestions / idées brillantes? :-)
Réponses:
production
Performance par rapport à la solution Perl de Jonathan sur un fichier de 10000 lignes
EDIT par Ed Morton (@ ghostdog74 n'hésitez pas à supprimer si vous désapprouvez).
Peut-être que cette version avec des noms de variables plus explicites aidera à répondre à certaines des questions ci-dessous et à clarifier généralement ce que fait le script. Il utilise également des tabulations comme séparateur que l'OP avait initialement demandé afin de gérer les champs vides et cela par coïncidence embellit un peu la sortie pour ce cas particulier.
Les solutions ci-dessus fonctionneront dans n'importe quel awk (sauf l'ancien awk cassé bien sûr - il y a YMMV).
Les solutions ci-dessus lisent cependant le fichier entier en mémoire - si les fichiers d'entrée sont trop volumineux pour cela, vous pouvez le faire:
qui n'utilise presque pas de mémoire mais lit le fichier d'entrée une fois par nombre de champs sur une ligne, il sera donc beaucoup plus lent que la version qui lit le fichier entier en mémoire. Il suppose également que le nombre de champs est le même sur chaque ligne et utilise GNU awk pour
ENDFILE
etARGIND
mais tout awk peut faire la même chose avec des tests surFNR==1
etEND
.la source
Une autre option consiste à utiliser
rs
:-c
modifie le séparateur de colonne d'entrée,-C
modifie le séparateur de colonne de sortie et-T
transpose les lignes et les colonnes. Ne pas utiliser à la-t
place de-T
, car il utilise un nombre de lignes et de colonnes calculé automatiquement qui n'est généralement pas correct.rs
, qui porte le nom de la fonction de remodelage dans APL, est fourni avec les BSD et OS X, mais il devrait être disponible auprès des gestionnaires de paquets sur d'autres plates-formes.Une deuxième option consiste à utiliser Ruby:
Une troisième option consiste à utiliser
jq
:jq -R .
imprime chaque ligne d'entrée en tant que littéral de chaîne JSON,-s
(--slurp
) crée un tableau pour les lignes d'entrée après avoir analysé chaque ligne en tant que JSON, et-r
(--raw-output
) génère le contenu des chaînes au lieu des littéraux de chaîne JSON. L'/
opérateur est surchargé pour fractionner les chaînes.la source
rs
- merci pour le pointeur! (Le lien est vers Debian; l'amont semble être mirbsd.org/MirOS/dist/mir/rs )rs
qui vient avec OS X,-c
seul définit le séparateur de colonne d'entrée sur un onglet.$'\t'
TTC TTA TTC TTC TTT
, courirrs -c' ' -C' ' -T < rows.seq > cols.seq
donners: no memory: Cannot allocate memory
. Il s'agit d'un système exécutant FreeBSD 11.0-RELEASE avec 32 Go de RAM. Donc, je suppose quers
tout place dans la RAM, ce qui est bon pour la vitesse, mais pas pour les données volumineuses.Une solution Python:
Ce qui précède est basé sur les éléments suivants:
Ce code suppose que chaque ligne a le même nombre de colonnes (aucun remplissage n'est effectué).
la source
l.split()
parl.strip().split()
(Python 2.7), sinon la dernière ligne de la sortie est paralysée. Fonctionne pour les séparateurs de colonnes arbitraires, utilisezl.strip().split(sep)
etsep.join(c)
si votre séparateur est stocké dans une variablesep
.le projet transpose sur sourceforge est un programme C de type coreutil pour exactement cela.
la source
-b
et-f
.Pure BASH, pas de processus supplémentaire. Un bel exercice:
la source
printf "%s\t" "${array[$COUNTER]}"
Jetez un œil au datamash GNU qui peut être utilisé comme
datamash transpose
. Une future version prendra également en charge la tabulation croisée (tableaux croisés dynamiques)la source
Voici un script Perl moyennement solide pour faire le travail. Il existe de nombreuses analogies structurelles avec la
awk
solution de @ ghostdog74 .Avec la taille des données de l'échantillon, la différence de performance entre perl et awk était négligeable (1 milliseconde sur 7 au total). Avec un ensemble de données plus grand (matrice 100x100, entrées de 6 à 8 caractères chacune), perl a légèrement surpassé awk - 0,026s contre 0,042s. Ni l'un ni l'autre ne posera probablement de problème.
Timings représentatifs pour Perl 5.10.1 (32 bits) vs awk (version 20040207 lorsque '-V') vs gawk 3.1.7 (32 bits) sur MacOS X 10.5.8 sur un fichier contenant 10000 lignes avec 5 colonnes par ligne:
Notez que gawk est beaucoup plus rapide que awk sur cette machine, mais toujours plus lent que perl. De toute évidence, votre kilométrage variera.
la source
Si vous avez
sc
installé, vous pouvez faire:la source
sc
nomme ses colonnes comme un ou une combinaison de deux caractères. La limite est26 + 26^2 = 702
.Il existe un utilitaire spécialement conçu pour cela,
Utilitaire de datamash GNU
Tiré de ce site, https://www.gnu.org/software/datamash/ et http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods
la source
En supposant que toutes vos lignes ont le même nombre de champs, ce programme awk résout le problème:
En mots, lorsque vous bouclez sur les lignes, pour chaque champ,
f
développez une chaîne séparée par un «:»col[f]
contenant les éléments de ce champ. Une fois que vous avez terminé avec toutes les lignes, imprimez chacune de ces chaînes sur une ligne distincte. Vous pouvez ensuite remplacer «:» pour le séparateur souhaité (par exemple, un espace) en acheminant la sortie à traverstr ':' ' '
.Exemple:
la source
GNU datamash est parfaitement adapté à ce problème avec une seule ligne de code et une taille de fichier potentiellement arbitrairement grande!
la source
Une solution perl hackish peut être comme ça. C'est bien car il ne charge pas tous les fichiers en mémoire, imprime les fichiers temporaires intermédiaires, puis utilise la pâte merveilleuse
la source
La seule amélioration que je peux voir pour votre propre exemple est l'utilisation de awk, qui réduira le nombre de processus exécutés et la quantité de données acheminées entre eux:
la source
J'utilise normalement ce petit
awk
extrait de code pour cette exigence:Cela charge simplement toutes les données dans un tableau bidimensionnel
a[line,column]
, puis les réimprime en tant quea[column,line]
, de sorte qu'il transpose l'entrée donnée.Cela doit garder une trace du
max
nombre imum de colonnes du fichier initial, de sorte qu'il soit utilisé comme nombre de lignes à imprimer.la source
J'ai utilisé la solution de fgm (merci fgm!), Mais j'avais besoin d'éliminer les caractères de tabulation à la fin de chaque ligne, donc j'ai modifié le script ainsi:
la source
Je cherchais juste un tranpose bash similaire mais avec un support pour le rembourrage. Voici le script que j'ai écrit basé sur la solution de fgm, qui semble fonctionner. Si cela peut être utile ...
la source
Je cherchais une solution pour transposer tout type de matrice (nxn ou mxn) avec tout type de données (nombres ou données) et j'ai obtenu la solution suivante:
la source
Si vous ne voulez extraire qu'une seule ligne (délimitée par des virgules) $ N d'un fichier et la transformer en colonne:
la source
Pas très élégant, mais cette commande "sur une seule ligne" résout le problème rapidement:
Ici cols est le nombre de colonnes, où vous pouvez remplacer 4 par
head -n 1 input | wc -w
.la source
Une autre
awk
solution et une entrée limitée avec la taille de la mémoire dont vous disposez.Cela joint chaque même position de numéro de fichier dans ensemble et
END
imprime le résultat qui serait la première ligne de la première colonne, la deuxième ligne de la deuxième colonne, etc.la source
Certains utilitaires standard * nix one-liners, aucun fichier temporaire n'est nécessaire. NB: le PO voulait une solution efficace , (c'est-à-dire plus rapide), et les premières réponses sont généralement plus rapides que cette réponse. Ces one-liners sont pour ceux qui aiment les outils logiciels * nix , pour quelque raison que ce soit. Dans de rares cas ( par exemple, E / S et mémoire rares), ces extraits peuvent en fait être plus rapides que certaines des principales réponses.
Appelez le fichier d'entrée foo .
Si nous savons que foo a quatre colonnes:
Si nous ne savons pas combien de colonnes foo a:
xargs
a une taille limite et ferait donc un travail incomplet avec un long fichier. Quelle taille limite dépend du système, par exemple:tr
&echo
:... ou si le nombre de colonnes est inconnu:
L'utilisation
set
, qui aimexargs
, a des limitations similaires basées sur la taille de la ligne de commande:la source
awk
.cut
,head
,echo
, Etc. ne sont pas plus POSIX code shell compatible qu'unawk
script est - ils sont tous en standard sur chaque installation UNIX. Il n'y a tout simplement aucune raison d'utiliser un ensemble d'outils qui, en combinaison, vous obligent à faire attention au contenu de votre fichier d'entrée et au répertoire à partir duquel vous exécutez le script lorsque vous pouvez simplement utiliser awk et que le résultat final est plus rapide et plus robuste .for f in cut head xargs seq awk ; do wc -c $(which $f) ; done
lorsque le stockage est trop lent ou que les E / S sont trop faibles, les interprètes plus gros aggravent les choses, quelle que soit leur qualité dans des circonstances plus idéales. Raison n ° 2: awk (ou la plupart des langages) souffre également d'une courbe d'apprentissage plus raide qu'un petit utilitaire conçu pour bien faire une chose. Lorsque le temps d'exécution est moins cher que les heures de travail du codeur, un codage facile avec des «outils logiciels» permet d'économiser de l'argent.une autre version avec
set
eval
la source
Une autre variante bash
Scénario
Production
la source
Voici une solution Haskell. Une fois compilé avec -O2, il fonctionne légèrement plus vite que awk de ghostdog et légèrement plus lent que le python
c finement enveloppéde Stephan sur ma machine pour les lignes d'entrée répétées "Hello world". Malheureusement, le support de GHC pour passer le code de ligne de commande est inexistant pour autant que je sache, vous devrez donc l'écrire dans un fichier vous-même. Il tronquera les lignes à la longueur de la ligne la plus courte.la source
Une solution awk qui stocke l'ensemble de la baie en mémoire
Mais nous pouvons "parcourir" le fichier autant de fois que les lignes de sortie sont nécessaires:
Qui (pour un faible nombre de lignes de sortie est plus rapide que le code précédent).
la source
Voici un one-liner Bash basé sur la simple conversion de chaque ligne en colonne et
paste
leur assemblage:m.txt:
crée un
tmp1
fichier pour qu'il ne soit pas vide.lit chaque ligne et la transforme en colonne en utilisant
tr
colle la nouvelle colonne dans le
tmp1
fichierles copies sont renvoyées dans
tmp1
.PS: Je voulais vraiment utiliser des io-descripteurs mais je n'ai pas pu les faire fonctionner.
la source
Un oneliner utilisant R ...
la source
J'ai utilisé ci-dessous deux scripts pour effectuer des opérations similaires auparavant. Le premier est en awk qui est beaucoup plus rapide que le second qui est en bash "pur". Vous pourrez peut-être l'adapter à votre propre application.
la source