J'ai un gros fichier texte (~ 50 Go lorsque gz'ed). Le fichier contient des 4*N
lignes ou des N
enregistrements; c'est-à-dire que chaque enregistrement se compose de 4 lignes. Je voudrais diviser ce fichier en 4 fichiers plus petits, chacun représentant environ 25% du fichier d'entrée. Comment puis-je diviser le fichier à la limite de l'enregistrement?
Une approche naïve serait zcat file | wc -l
d'obtenir le nombre de lignes, de diviser ce nombre par 4, puis de l'utiliser split -l <number> file
. Cependant, cela passe deux fois sur le fichier et le compteur de lignes est extrêmement lent (36 minutes). Y a-t-il une meilleure façon?
Cela se rapproche, mais ce n'est pas ce que je recherche. La réponse acceptée fait également un décompte de lignes.
ÉDITER:
Le fichier contient des données de séquençage au format fastq. Deux enregistrements ressemblent à ceci (anonymisés):
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
La première ligne de chaque enregistrement commence par un @
.
EDIT2:
zcat file > /dev/null
prend 31 minutes.
EDIT3:
Seule la première ligne commence par @
. Aucun des autres ne le sera jamais. Voyez ici . Les dossiers doivent rester en ordre. Ce n'est pas correct d'ajouter quoi que ce soit au fichier résultant.
zcat file > /dev/null
?@
et aussi qu'il y a 4 lignes par enregistrement. Ces deux sont-ils absolus? - et les lignes 2,3,4 peuvent-elles commencer par@
? et y a-t-il un en-tête non enregistré de lignes de pied de page dans le fichier?Réponses:
Je ne pense pas que vous puissiez le faire - pas de manière fiable et pas de la façon dont vous le demandez. Le fait est que le taux de compression de l'archive ne sera probablement pas réparti uniformément de la tête à la queue - l'algorithme de compression s'appliquera mieux à certaines parties qu'à d'autres. Voilà comment ça fonctionne. Et vous ne pouvez donc pas prendre en compte votre division sur la taille du fichier compressé.
De plus,
gzip
ne prend tout simplement pas en charge le stockage de la taille d'origine des fichiers compressés supérieurs à 4 Go - il ne peut pas le gérer. Et vous ne pouvez donc pas interroger l'archive pour obtenir une taille fiable, car cela vous trompera.La chose à 4 lignes - c'est assez facile, vraiment. La chose à 4 fichiers - je ne sais tout simplement pas comment vous pouvez le faire de manière fiable et avec une distribution uniforme sans d'abord extraire l'archive pour obtenir sa taille non compressée. Je ne pense pas que vous puissiez le faire parce que j'ai essayé.
Cependant, ce que vous pouvez faire, c'est définir une taille maximale pour les fichiers de sortie divisés et assurez-vous qu'ils sont toujours cassés aux barrières d'enregistrement. Vous pouvez facilement le faire. Voici un petit script qui le fera en extrayant l'
gzip
archive et en canalisant le contenu à travers quelquesdd
tampons de canal explicites avec descount=$rpt
arguments spécifiques , avant de le passerlz4
pour décompresser / recompresser chaque fichier à la volée. J'ai également ajouté quelquestee
astuces pour imprimer les quatre dernières lignes de chaque segment sur stderr également.Cela continuera simplement jusqu'à ce qu'il ait traité toutes les entrées. Il ne tente pas de le diviser par un certain pourcentage - ce qu'il ne peut pas obtenir - mais à la place, il le divise par un nombre maximal d'octets bruts par division. Et de toute façon, une grande partie de votre problème est que vous ne pouvez pas obtenir une taille fiable sur votre archive car elle est trop grande - quoi que vous fassiez, ne recommencez pas - faites des divisions de moins de 4 Go par morceau ce tour , peut être. Ce petit script, au moins, vous permet de le faire sans avoir à écrire un octet non compressé sur le disque.
Voici une version plus courte, dépouillée de l'essentiel - elle n'ajoute pas tous les éléments du rapport:
Il fait toutes les mêmes choses que le premier, surtout, il n'a tout simplement pas grand-chose à dire à ce sujet. De plus, il y a moins d'encombrement, il est donc plus facile de voir ce qui se passe, peut-être.
Le
IFS=
problème est simplement de gérer uneread
ligne par itération. Nousread
un parce que nous avons besoin que notre boucle se termine lorsque l'entrée se termine. Cela dépend de la taille de votre enregistrement - qui, selon votre exemple, est de 354 octets par. J'ai créé unegzip
archive 4 + gb avec des données aléatoires afin de la tester.Les données aléatoires ont été obtenues de cette façon:
... mais peut-être que vous n'avez pas à vous en préoccuper autant, car vous avez déjà les données et tout. Retour à la solution ...
Fondamentalement
pigz
- qui semble décompresser un peu plus vite que le faitzcat
- dirige le flux non compressé et lesdd
tampons qui sortent en blocs d'écriture dimensionnés spécifiquement à un multiple de 354 octets. La boucleread
une$line
fois chaque itération de test d' entrée est encore arriver, qu'elleprintf
ensuiteprintf
aulz4
devant un autredd
est appelé pour lire des blocs de taille spécifiquement à un multiple de 354 octets - pour synchroniser avec le tampondd
procédé - pour la durée. Il y aura une courte lecture par itération en raison de l'initialeread $line
- mais cela n'a pas d'importance, parce que nous l'imprimons danslz4
- notre processus de collecte - de toute façon.Je l'ai configuré de sorte que chaque itération lira environ 1 Go de données non compressées et les compressera en flux à environ 650 Mo environ.
lz4
est beaucoup plus rapide que n'importe quelle autre méthode de compression utile - c'est la raison pour laquelle je l'ai choisie ici parce que je n'aime pas attendre.xz
ferait un bien meilleur travail à la compression réelle, probablement, cependant. Une choselz4
, cependant, est qu'il peut souvent décompresser à des vitesses proches de la RAM - ce qui signifie que vous pouvez décompresser unelz4
archive très rapidement, car vous pourriez de toute façon l'écrire en mémoire.Le grand fait quelques rapports par itération. Les deux boucles imprimeront
dd
le rapport sur le nombre d'octets bruts transférés et la vitesse et ainsi de suite. La grande boucle affichera également les 4 dernières lignes d'entrée par cycle, et un nombre d'octets pour celui-ci, suivi d'unls
répertoire dans lequel j'écris leslz4
archives. Voici quelques tours de sortie:la source
gzip -l
ne fonctionne que pour les fichiers <2GiB non compressés IIRC (quelque chose de plus petit que le fichier OP de toute façon).Le fractionnement des fichiers sur les limites des enregistrements est en fait très facile, sans aucun code:
Cela créera des fichiers de sortie de 10000 lignes chacun, avec les noms nom_sortie_aa, nom_sortie_ab, nom_sortie_ac, ... Avec une entrée aussi grande que la vôtre, cela vous donnera beaucoup de fichiers de sortie. Remplacez-le
10000
par un multiple de quatre et vous pouvez rendre les fichiers de sortie aussi grands ou petits que vous le souhaitez. Malheureusement, comme avec les autres réponses, il n'y a pas de bon moyen de garantir que vous obtiendrez le nombre souhaité de taille (approximativement) égale de fichiers de sortie sans faire quelques suppositions sur l'entrée. (Ou, en fait,wc
vous faites passer le tout .) Si vos enregistrements sont de taille à peu près égale (ou au moins, distribués de manière à peu près égale), vous pouvez essayer de trouver une estimation comme celle-ci:Cela vous indiquera la taille compressée des 1000 premiers enregistrements de votre fichier. Sur cette base, vous pouvez probablement trouver une estimation du nombre de lignes que vous souhaitez dans chaque fichier pour aboutir à quatre fichiers. (Si vous ne voulez pas qu'il reste un cinquième fichier dégénéré, assurez-vous de remplir un peu votre estimation ou préparez-vous à coller le cinquième fichier à la fin du quatrième.)
Modifier: Voici une autre astuce, en supposant que vous souhaitez des fichiers de sortie compressés:
Cela créera beaucoup de fichiers plus petits, puis les regroupera rapidement. (Vous devrez peut-être modifier le paramètre -l en fonction de la longueur des lignes de vos fichiers.) Il suppose que vous disposez d'une version relativement récente de coreutils GNU (pour split --filter) et d'environ 130% de la taille de votre fichier d'entrée dans Espace disque libre. Remplacez gzip / zcat par pigz / unpigz si vous ne les avez pas. J'ai entendu dire que certaines bibliothèques de logiciels (Java?) Ne peuvent pas gérer les fichiers gzip concaténés de cette façon, mais je n'ai eu aucun problème jusqu'à présent. (pigz utilise la même astuce pour paralléliser la compression.)
la source
D'après ce que j'ai rassemblé après avoir vérifié la google-sphère et testé un
.gz
fichier de 7,8 Gio , il semble que les métadonnées de la taille du fichier non compressé d'origine ne soient pas précises (c'est-à-dire incorrectes ) pour les gros.gz
fichiers (supérieurs à 4 Gio (peut-être 2 Gio pour certains). versionsgzip
).Re mon de test des métadonnées de gzip.:
Il semble donc qu'il ne soit pas possible de détruire la taille non compressée sans réellement la décompresser (ce qui est pour le moins un peu rude!)
Quoi qu'il en soit, voici un moyen de diviser un fichier non compressé aux limites des enregistrements, où chaque enregistrement contient 4 lignes .
Il utilise la taille du fichier en octets (via
stat
) et enawk
comptant les octets (pas les caractères). Que la fin de ligne soit ou nonLF
|CR
|CRLF
, ce script gère la longueur de fin de ligne via une variable intégréeRT
).Voici le test que j'ai utilisé pour vérifier que le nombre de lignes de chaque fichier est
mod 4 == 0
Sortie de test:
myfile
a été généré par:la source
Ce n'est pas censé être une réponse sérieuse! Je viens de jouer avecflex
et cela ne fonctionnera probablement pas sur un fichier d'entrée avec ~ 50 Go (le cas échéant, sur des données d'entrée plus grandes que mon fichier de test):Cela fonctionne pour moi sur un fichier ~ 1 Go input.txt :
Étant donné le
flex
fichier d'entrée splitter.l :générer lex.yy.c et le compiler en
splitter
binaire avec:Usage:
Temps d'exécution pour 1 Go d'entrée.txt :
la source
getc(stream)
et d'appliquer une logique simple. Savez-vous également que le. (point) le caractère regex dans (f) lex correspond à n'importe quel caractère sauf le saut de ligne , non? Alors que ces enregistrements sont multilignes.@
caractère, puis laisser la règle par défaut copier les données. Vous avez maintenant votre règle qui copie une partie des données comme un gros jeton, puis la règle par défaut qui obtient la deuxième ligne un caractère à la fois.txr
.Voici une solution en Python qui fait passer un fichier d'entrée en écrivant les fichiers de sortie au fur et à mesure.
Une caractéristique de l'utilisation
wc -l
est que vous supposez que chacun des enregistrements est de la même taille. C'est peut-être vrai ici, mais la solution ci-dessous fonctionne même lorsque ce n'est pas le cas. Il s'agit essentiellement de l'utilisationwc -c
ou du nombre d'octets dans le fichier. En Python, cela se fait via os.stat ()Voici donc comment fonctionne le programme. Nous calculons d'abord les points de partage idéaux sous forme de décalages d'octets. Ensuite, vous lisez les lignes du fichier d'entrée en écrivant dans le fichier de sortie approprié. Lorsque vous voyez que vous avez dépassé le prochain point de partage optimal et que vous êtes à une limite d'enregistrement, fermez le dernier fichier de sortie et ouvrez le suivant.
Le programme est optimal dans ce sens, il lit une fois les octets du fichier d'entrée; Obtenir la taille du fichier ne nécessite pas de lire les données du fichier. Le stockage nécessaire est proportionnel à la taille d'une ligne. Mais Python ou le système ont probablement des tampons de fichiers raisonnables pour accélérer les E / S.
J'ai ajouté des paramètres pour le nombre de fichiers à diviser et la taille d'enregistrement au cas où vous souhaiteriez ajuster cela à l'avenir.
Et clairement, cela pourrait également être traduit dans d'autres langages de programmation.
Une autre chose, je ne sais pas si Windows avec son crlf gère correctement la longueur de la ligne comme il le fait sur les systèmes Unix-y. Si len () est désactivé par un ici, j'espère qu'il est évident de savoir comment ajuster le programme.la source
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
L'utilisateur FloHimself semblait curieux d'une solution TXR . En voici un qui utilise le TXR Lisp intégré :
Remarques:
Pour la même raison
pop
, il est important de taper chaque tuple de la liste paresseuse de tuples pour que la liste paresseuse soit consommée. Nous ne devons pas conserver une référence au début de cette liste, car la mémoire augmentera alors que nous parcourons le fichier.(seek-stream fo 0 :from-current)
est un cas sans opérationseek-stream
, qui se rend utile en retournant la position actuelle.Performance: ne le mentionnez pas. Utilisable, mais n'apportera aucun trophée à la maison.
Étant donné que nous ne vérifions la taille que tous les 1000 tuples, nous pourrions simplement créer une ligne de 4000 lignes.
la source
Si vous n'avez pas besoin que les nouveaux fichiers soient des morceaux contigus du fichier d'origine, vous pouvez le faire entièrement de
sed
la manière suivante:L'
-n
empêche d'imprimer chaque ligne, et chacun des-e
scripts fait essentiellement la même chose.1~16
correspond à la première ligne et toutes les 16 lignes après.,+3
signifie faire correspondre les trois lignes suivantes après chacune d'elles.w1.txt
dit d'écrire toutes ces lignes dans le fichier1.txt
. Cela prend chaque 4ème groupe de 4 lignes et l'écrit dans un fichier, en commençant par le premier groupe de 4 lignes. Les trois autres commandes font la même chose, mais elles sont chacune décalées vers l'avant de 4 lignes et écrivent dans un fichier différent.Cela se cassera horriblement si le fichier ne correspond pas exactement aux spécifications que vous avez définies, mais sinon cela devrait fonctionner comme vous le vouliez. Je ne l'ai pas profilé, donc je ne sais pas à quel point ce sera efficace, mais il
sed
est raisonnablement efficace pour l'édition de flux.la source