Combinant une grande quantité de files

J'ai ± 10 000 files ( res.1res.10000 ), tous composés d'une colonne et d'un nombre égal de lignes. Ce que je veux est, en substance, simple; merge tous les files en colonne dans un nouveau file final.res . J'ai essayé d'utiliser:

paste res.*

Cependant (bien que cela semble fonctionner pour un petit sous-set de files de résultats, cela donne l'erreur suivante lorsqu'il est effectué sur l'set: Too many open files .

Il doit y avoir un moyen "facile" d'y parvenir, mais malheureusement, je suis un peu nouveau pour Unix. Merci d'avance!

PS: Pour vous donner une idée de ce que (un de mes) file (s) de données ressemble à:

 0.5 0.5 0.03825 0.5 10211.0457 10227.8469 -5102.5228 0.0742 3.0944 ... 

Si vous disposez d'permissions root sur cette machine, vous pouvez augmenter temporairement la limite "nombre maximal de descripteurs de files ouverts":

 ulimit -Hn 10240 # The hard limit ulimit -Sn 10240 # The soft limit 

Et alors

 paste res.* >final.res 

Après cela, vous pouvez le rétablir aux valeurs d'origine.


Une deuxième solution , si vous ne pouvez pas changer la limite:

 for f in res.*; do cat final.res | paste - $f >temp; cp temp final.res; done; rm temp 

Il appelle paste pour chaque file une fois, et à la fin il y a un énorme file avec toutes les colonnes (il prend sa minute).

Edit : Utilisation inutile du chatNon !

Comme mentionné dans les commentaires, l'utilisation de cat ici ( cat final.res | paste - $f >temp ) n'est pas inutile. La première fois que la boucle s'exécute, le file final.res n'existe pas déjà. paste échouerait alors et le file n'est jamais rempli, ni créé. Avec ma solution cat seul échoue la première fois avec un No such file or directory et paste lit à partir de stdin juste un file vide, mais il continue. L'erreur peut être ignorée.

Si la réponse chaos n'est pas applicable (parce que vous ne disposez pas des permissions requirejses), vous pouvez mettre en lot les appels de paste comme suit:

 ls -1 res.* | split -l 1000 -d - lists for list in lists*; do paste $(cat $list) > merge${list##lists}; done paste merge* > final.res 

Cette list répertorie les files 1000 à la fois dans les files nommés lists00 , lists01 etc., puis colle la res. correspondante res. files dans les files nommés merge00 , merge01 etc., et fusionne finalement tous les files partiellement fusionnés résultants.

Comme mentionné par le chaos, vous pouvez augmenter le nombre de files utilisés à la fois; la limite est la valeur donnée ulimit -n less cependant beaucoup de files que vous avez déjà ouverts, donc vous diriez

 ls -1 res.* | split -l $(($(ulimit -n)-10)) -d - lists 

utiliser la limite less dix.

Si votre version de split ne supporte pas -d , vous pouvez le supprimer: tout ce qu'il fait est de dire split pour utiliser des suffixes numériques. Par défaut, les suffixes seront aa , ab etc. au lieu de 01 , 02 etc.

S'il y a autant de files que ls -1 res.* Échoue ("list d'arguments trop longue"), vous pouvez le replace par find qui évitera cette erreur:

 find . -maxdepth 1 -type f -name res.\* | split -l 1000 -d - lists 

(Comme le souligne don_crissti , -1 ne devrait pas être nécessaire lors de la sortie de ls , mais je le laisse pour gérer les cas où ls est aliasé avec -C .)

Essayez de l'exécuter de cette façon:

 ls res.*|xargs paste >final.res 

Vous pouvez également split le lot en plusieurs parties et essayer quelque chose comme:

 paste `echo res.{1..100}` >final.100 paste `echo res.{101..200}` >final.200 ... 

et à la fin combiner les files finaux

 paste final.* >final.res 

Compte tenu de la quantité de files, de tailles de lignes, etc. impliqués, je pense que cela va dépasser les tailles par défaut des outils (awk, sed, paste, *, etc.)

Je créerais un petit programme pour cela, il n'aurait pas 10 000 files ouverts, ni une ligne de plusieurs centaines de milliers (10 000 files de 10 (taille maximale de la ligne dans l'exemple)). Il ne nécessite qu'un ~ 10 000 tableau d'entiers, pour stocker le nombre d'octets ont été lus dans chaque file. L'inconvénient est qu'il n'a qu'un seul descripteur de file, il est réutilisé pour chaque file, pour chaque ligne, et cela pourrait être lent.

Les définitions de FILES et ROWS doivent être changées en valeurs exactes réelles. La sortie est envoyée à la sortie standard.

 #include <stdio.h> #include <stdlib.h> #include <ssortingng.h> #define FILES 10000 /* number of files */ #define ROWS 500 /* number of rows */ int main() { int positions[FILES + 1]; FILE *file; int r, f; char filename[100]; size_t linesize = 100; char *line = (char *) malloc(linesize * sizeof(char)); for (f = 1; f <= FILES; positions[f++] = 0); /* sets the initial positions to zero */ for (r = 1; r <= ROWS; ++r) { for (f = 1; f <= FILES; ++f) { sprintf(filename, "res.%d", f); /* creates the name of the current file */ file = fopen(filename, "r"); /* opens the current file */ fseek(file, positions[f], SEEK_SET); /* set position from the saved one */ positions[f] += getline(&line, &linesize, file); /* reads line and saves the new position */ line[strlen(line) - 1] = 0; /* removes the newline */ printf("%s ", line); /* prints in the standard ouput, and a single space */ fclose(file); /* closes the current file */ } printf("\n"); /* after getting the line from each file, prints a new line to standard output */ } } 
 i=0 { paste res.? res.?? res.??? while paste ./res."$((i+=1))"[0-9][0-9][0-9] do :; done; } >outfile 

Je ne pense pas que ce soit aussi compliqué que cela – vous avez déjà fait le travail en commandant les noms de files. Il suffit de ne pas ouvrir tous en même time, c'est tout.

Autrement:

 pst() if shift "$1" then paste "$@" fi set ./res.* while [ -n "${1024}" ] || ! paste "$@" do pst "$(($#-1023))" "$@" shift 1024 done >outfile 

… mais je pense que cela les fait reculer … Cela pourrait fonctionner mieux:

 i=0; echo 'while paste \' until [ "$((i+=1))" -gt 1023 ] && printf '%s\n' '"${1024}"' \ do\ shift\ 1024 done do echo '"${'"$i"'-/dev/null}" \' done | sh -s -- ./res.* >outfile 

Et voici encore une autre façon:

 tar --no-recursion -c ./ | { printf \\0; tr -s \\0; } | cut -d '' -f-2,13 | tr '\0\n' '\n\t' >outfile 

Cela permet à tar de rassembler tous les files dans un stream délimité par un caractère nul, d'parsingr toutes les métadonnées de l'en-tête mais le nom du file et de transformer toutes les lignes de tous les files en tabs. Il s'appuie sur l'input étant des files de text réels – ce qui signifie que chaque extrémité w / une nouvelle ligne et il n'y a pas d'octets NULL dans les files. Oh – et il s'appuie également sur les noms de files eux-mêmes étant newline-free (bien que cela pourrait être géré avec --xform option --xform GNU tar ) . Étant donné que ces conditions sont remplies, il devrait faire très peu de travail sur un nombre quelconque de files – et tar fera presque tout.

Le résultat est un set de lignes qui ressemblent à:

 ./fname1 C1\tC2\tC3... ./fname2 C1\tC2\t... 

Etc.

Je l'ai testé en créant d'abord 5 files de test. Je n'avais pas vraiment envie de générer 10000 files en ce moment, alors je suis juste allé un peu plus grand pour chacun – et j'ai également veillé à ce que la longueur des files diffère énormément. Ceci est important lors du test des scripts tar car tar va bloquer les inputs à des longueurs fixes – si vous n'essayez pas au less quelques longueurs différentes, vous ne saurez jamais si vous ne traiterez que le seul.

Quoi qu'il en soit, pour les files de test que j'ai fait:

 for f in 1 2 3 4 5; do : >./"$f" seq "${f}000" | tee -a [12345] >>"$f" done 

ls ensuite rapporté:

 ls -sh [12345] 68K 1 68K 2 56K 3 44K 4 24K 5 

… alors j'ai couru …

 tar --no-recursion -c ./ | { printf \\0; tr -s \\0; }| cut -d '' -f-2,13 | tr '\0\n' '\n\t' | cut -f-25 

… juste pour ne montrer que les 25 premiers champs délimités par des tabulations par ligne (car chaque file est une seule ligne – il y en a beaucoup )

La sortie était:

 ./1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ./2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ./3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ./4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ./5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25