Comment puis-je diviser un fichier texte en plusieurs fichiers texte?


16

J'ai un fichier texte appelé entry.txtqui contient les éléments suivants:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Je voudrais le diviser en trois fichiers texte: entry1.txt, entry2.txt, entry3.txt. Leur contenu est le suivant.

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

En d'autres termes, le [caractère indique qu'un nouveau fichier doit commencer. Les entrées ( [ entry*], où *est un entier) sont toujours dans l'ordre numérique et sont des entiers consécutifs commençant de 1 à N (dans mon fichier d'entrée réel, N = 200001).

Existe-t-il un moyen de fractionner automatiquement un fichier texte en bash? Mon entrée entry.txtréelle contient en fait 200 001 entrées.

Réponses:


11

Et voici une belle doublure simple et stupéfiante:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

Cela fonctionnera pour n'importe quelle taille de fichier, quel que soit le nombre de lignes dans chaque entrée, tant que chaque en-tête d'entrée ressemble [ blahblah blah blah ]. Remarquez l'espace juste après l'ouverture [et juste avant la fermeture ].


EXPLICATION:

awket gawklire un fichier d'entrée ligne par ligne. Au fur et à mesure que chaque ligne est lue, son contenu est enregistré dans la $0variable. Ici, nous disons de gawkfaire correspondre n'importe quoi entre crochets et d'enregistrer sa correspondance dans le tableau k.

Ainsi, chaque fois que cette expression régulière est mise en correspondance, c'est-à-dire que pour chaque en-tête de votre fichier, k [1] aura la région correspondante de la ligne. A savoir, "entrée1", "entrée2" ou "entrée3" ou "entréeN".

Enfin, nous imprimons chaque ligne dans un fichier appelé <whatever value k currently has>.txt, c'est-à-dire entry1.txt, entry2.txt ... entryN.txt.

Cette méthode sera beaucoup plus rapide que perl pour les fichiers plus volumineux.


+1 sympa. Vous n'avez pas besoin de matchl'entrée: /^\[/ { name=$2 }devrait suffire.
Thor

Merci @Thor. Votre suggestion est correcte pour le cas décrit, mais elle suppose qu'il n'y a jamais d'espace dans le nom de l'entrée. C'est pourquoi j'ai utilisé l'exemple [ blahblah blah blah ]dans ma réponse.
terdon

Ah, j'ai raté un peu les entrées séparées par des espaces. Vous pouvez également accueillir ceux avec FS, par exemple -F '\\[ | \\]'.
Thor

@terdon J'aime vraiment ces solutions courtes, malheureusement je n'arrive généralement pas à les généraliser à mes besoins. Pourriez-vous me donner un coup de main? Mon fichier comporte des lignes commençant par #S x, où x est un nombre à 1, 2 ou 3 chiffres. Il suffit de les enregistrer dans x.dat. J'ai essayé: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtet quelques variantes de cela.
mikuszefski

Il a gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtfait l'affaire. 2Cependant, je ne comprends pas très bien le numéro du tableau .
mikuszefski

17

Avec csplit de GNU coreutils (Linux non embarqué, Cygwin):

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

Vous vous retrouverez avec un fichier vide supplémentaire entry0.txt(contenant la partie avant le premier en-tête).

Le csplit standard n'a pas le {*}répéteur indéfini et la -bpossibilité de spécifier le format du suffixe, donc sur d'autres systèmes, vous devrez d'abord compter le nombre de sections et renommer les fichiers de sortie par la suite.

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

je trouve que csplit est un peu excentrique de temps en temps, mais incroyablement utile quand je veux faire ce genre de chose.
ixtmixilix

10

En perl, cela peut être fait beaucoup plus simplement:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

Voici un court one-liner awk:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

Comment cela marche-t-il?

  • /^\[/ correspond aux lignes commençant par un crochet carré gauche, et
  • {ofn=$2 ".txt"}définit une variable sur le deuxième mot délimité par des espaces blancs comme nom de fichier de sortie. Alors,
  • ofn est une condition qui a la valeur true si la variable est définie (ce qui entraîne l'ignorance des lignes avant votre premier en-tête)
  • {print > ofn} redirige la ligne actuelle vers le fichier spécifié.

Notez que tous les espaces de ce script awk peuvent être supprimés, si la compacité vous rend heureux.

Notez également que le script ci-dessus a vraiment besoin que les en-têtes de section aient des espaces autour et non à l'intérieur. Si vous vouliez pouvoir gérer les en-têtes de section comme [foo]et [ this that ], vous auriez besoin d'un peu plus de code:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

Cela utilise la sub()fonction de awk pour supprimer les crochets de début et de fin plus les espaces. Notez que par comportement awk standard, cela réduira les espaces blancs (le séparateur de champ) en un seul espace (c'est [ this that ]-à- dire est enregistré dans "this that.txt"). Si la conservation de l'espace d'origine dans vos noms de fichiers de sortie est importante, vous pouvez expérimenter en définissant FS.


2

Cela peut être fait depuis la ligne de commande en python comme:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

C'est une façon un peu grossière, mais facile à comprendre: utilisez grep -l '[ entry ]' FILENAMEpour obtenir les numéros de ligne à diviser à [entrée]. Utilisez une combinaison tête et queue pour obtenir les bons morceaux.

Comme je l'ai dit; ce n'est pas joli, mais c'est facile à comprendre.


2

Qu'en est-il de l'utilisation de awk avec [comme séparateur d'enregistrement et de l' espace comme séparateur de champ. Cela nous donne facilement les données à mettre dans le fichier comme $0où il doit remettre le début supprimé [et le nom de fichier comme $1. Il ne nous reste alors plus qu'à gérer le cas particulier du 1er enregistrement qui est vide. Cela nous donne:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

La réponse de terdon fonctionne pour moi mais j'avais besoin d'utiliser gawk, pas awk. Le manuel de gawk (recherchez 'match (') explique que l'argument tableau dans match () est une extension gawk. Cela dépend peut-être de votre installation Linux et de vos versions awk / nawk / gawk mais sur ma machine Ubuntu seul gawk a exécuté l'excellent de terdon répondre:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

Voici une solution Perl. Ce script détecte les [ entryN ]lignes et modifie le fichier de sortie en conséquence, mais ne valide pas, n'analyse pas ou ne traite pas les données de chaque section, il imprime simplement la ligne d'entrée dans le fichier de sortie.

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

Salut, j'ai écrit ce script simple en utilisant ruby ​​pour résoudre votre problème

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

vous pouvez l'utiliser de cette façon:

ruby split.rb < entry.txt

je l'ai testé, et cela fonctionne très bien ..


1

Je préfère l' csplitoption, mais comme alternative, voici une solution GNU awk:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

Exécutez-le comme ceci:

gawk -f parse.awk entry.txt

1
FWIW, le RT variable semble être spécifique à gawk. Cette solution ne fonctionne pas pour moi en utilisant awk de FreeBSD.
ghoti

@ghoti: D'accord, j'aurais dû le mentionner. Je l'ai inclus dans la réponse maintenant. Merci.
Thor
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.