diviser le fichier en deux parties, selon un modèle

14

Comment diviser un gros fichier en deux parties, selon un modèle?

Donné un exemple file.txt:

ABC
EFG
XYZ
HIJ
KNL

Je veux diviser ce fichier en XYZtel qu'il file1contient des lignes vers le haut XYZet le reste des lignes file2.

d.putto
la source
La XYZligne doit-elle être incluse ou non dans la sortie?
terdon
@terdon Dans mon cas, aucune ligne "XYZ" ne devrait faire partie du fichier2. Mais si vous avez un moyen de le faire, veuillez ajouter à la réponse. Cela pourrait être utile dans certains autres cas.
d.putto
Assez juste, c'est fait.
terdon

Réponses:

10

Avec awkvous pouvez faire:

awk '{print >out}; /XYZ/{out="file2"}' out=file1 largefile


Explication: Le premier awkargument ( out=file1) définit une variable avec le nom de fichier qui sera utilisé pour la sortie pendant le largefiletraitement de l' argument suivant ( ). Le awkprogramme imprimera toutes les lignes dans le fichier spécifié par la variable out( {print >out}). Si le motif XYZest trouvé, la variable de sortie sera redéfinie pour pointer vers le nouveau fichier ( {out="file2}") qui sera utilisé comme cible pour imprimer les lignes de données suivantes.

Les références:

Janis
la source
14

C'est un travail pour csplit:

csplit -sf file -n 1 large_file /XYZ/

serait silently diviser le fichier, la création de pièces avec pré fix fileet numbered à l' aide d' un seul chiffre, par exemple , file0etc. Notez que l' utilisation /regex/décomposerait, mais ne comprenant pas la ligne qui correspond regex. Pour diviser jusqu'à et y compris la mise en correspondance de ligne regexajouter un +1décalage:

csplit -sf file -n 1 large_file /XYZ/+1

Cela crée deux fichiers file0et file1. Si vous avez absolument besoin qu'ils soient nommés file1et que file2vous puissiez toujours ajouter un modèle vide à la csplitcommande et supprimer le premier fichier:

csplit -sf file -n 1 large_file // /XYZ/+1

crée file0, file1et file2mais file0est vide de sorte que vous pouvez en toute sécurité supprimer:

rm -f file0
don_crissti
la source
C'est, je pense, la réponse la plus simple. Tout ce que vous avez à faire est de lister certains modèles et le fichier sera divisé par eux dans l'ordre. Brillant!
Henry Blyth
6

Avec une version moderne, kshvoici une variante de shell (c'est-à-dire sans sed) de l'une des sedréponses ci-dessus:

{ read in <##XYZ ; print "$in" ; cat >file2 ;} <largefile >file1


Et une autre variante en elle- kshmême (c'est-à-dire en omettant également la cat):

{ read in <##XYZ ; print "$in" ; { read <##"" ;} >file2 ;} <largefile >file1


(La kshsolution pure semble être assez performante; sur un fichier de test de 2,4 Go, elle a nécessité 19 à 21 secondes, contre 39 à 47 secondes avec l' approche sed/ cat).

Janis
la source
C'est très rapide. Mais je ne pense pas que vous en ayez besoin readet print- vous devriez simplement le laisser aller à la sortie qui lui est propre. Les performances s'améliorent si vous construisez entièrement la boîte à outils AST et kshcompilez tous les buildins - c'est bizarre pour moi que ce sedne soit pas l'un d'eux, en fait. Mais avec des trucs comme while <file doje suppose que vous n'avez pas besoin de sedtant de choses ...
mikeserv
Je suis curieux cependant - comment awkavez-vous performé dans votre benchmark? Et même si je suis sûr que vous kshgagnerez probablement toujours ce combat, si vous utilisez un GNU, sedvous n'êtes pas très juste sed- GNU's -unbuffered est une approche pauvre en pisse pour s'assurer POSIXLY que le décalage du descripteur est laissé là où le programme s'est arrêté. il - il ne devrait pas être nécessaire de ralentir le fonctionnement régulier du programme - la mise en mémoire tampon est très bien - tout seddevrait avoir à faire est de chercher le descripteur une fois terminé. Pour une raison quelconque, GNU renverse cette mentalité.
mikeserv
@mikeserv; La correspondance du motif de redirection est effectuée jusqu'à ce que le motif soit trouvé, et la ligne avec le motif trouvé ne sera pas imprimée si elle n'est pas explicitement effectuée comme illustré. (Au moins, cela a montré mon test.) Notez qu'il n'y a pas while; l'impression est implicitement effectuée comme l'effet secondaire défini de l' <##opérateur de redirection. Et seule la ligne correspondante doit être imprimée. ( De cette façon , la mise en œuvre de fonction de coque est plus flexible pour le soutien de incl./excl.) Une explicite whileboucle je pense être important plus lent (mais n'ai pas vérifié).
Janis
1
@mikeserv; Ah ok. BTW, j'ai juste essayé le headau lieu du read; il semble un peu plus lent, mais il code terser: { head -1 <##XYZ ; { read <##"" ;} >file4 ;} <largefile >file3.
Janis
1
@mikeserv; Bon point; ça ne l'était pas. Mais quand j'active la fonction intégrée (juste fait et vérifié les résultats), ce sont les mêmes chiffres, étrangement. (Peut-être une surcharge d'appels de fonction par rapport à la lecture?)
Janis
6
{ sed '/XYZ/q' >file1; cat >file2; } <infile

Avec GNU, sedvous devez utiliser le -ucommutateur nbuffered. La plupart des autres seds devraient tout de même fonctionner.

Pour exclure XYZ ...

{ sed -n '/XYZ/q;p'; cat >file2; } <infile >file1
mikeserv
la source
3

Essayez ceci avec GNU sed:

sed -n -e '1,/XYZ/w file1' -e '/XYZ/,${/XYZ/d;w file2' -e '}' large_file
Cyrus
la source
Plus court:sed -e '1,/XYZ/{w file1' -e 'd}' large_file > file2
don_crissti
1

Un hack facile consiste à imprimer sur STDOUT ou STDERR, selon que le motif cible a été mis en correspondance. Vous pouvez ensuite utiliser les opérateurs de redirection du shell pour rediriger la sortie en conséquence. Par exemple, en Perl, en supposant que le fichier d'entrée est appelé fet les deux fichiers de sortie f1et f2:

  1. Supprimer la ligne qui correspond au modèle de fractionnement:

    perl -ne 'if(/XYZ/){$a=1; next} ; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2
  2. Y compris la ligne assortie:

    perl -ne '$a=1 if /XYZ/; $a==1 ? print STDERR : print STDOUT;' f >f1 2>f2

Vous pouvez également imprimer dans différentes poignées de fichier:

  1. Supprimer la ligne qui correspond au modèle de fractionnement:

    perl -ne 'BEGIN{open($fh1,">","f1");open($fh2,">","f2");}
    if(/XYZ/){$a=1; next}$a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
  2. Y compris la ligne assortie:

    perl -ne 'BEGIN{open($fh1,">","f1"); open($fh2,">","f2");}
              $a=1 if /XYZ/; $a==1 ? print $fh1 "$_" : print $fh2 "$_";' f
terdon
la source