52 votes

Fractionnement d'un fichier sous Linux en fonction du contenu

J'ai un dump d'e-mails d'environ 400 Mo. Je veux diviser cela en fichiers .txt, constitués d'un courrier dans chaque fichier. Chaque e-mail commence par l'en-tête HTML standard spécifiant le type de document.

Cela signifie que je devrai diviser mes fichiers en fonction de l'en-tête ci-dessus. Comment faire sous Linux ?

90voto

kev Points 41855

Si vous avez un mail.txt

 $ cat mail.txt
<html>
    mail A
</html>

<html>
    mail B
</html>

<html>
    mail C
</html>

exécuter csplit pour diviser par <html>

 $ csplit mail.txt '/^<html>$/' '{*}'

 - mail.txt    => input file
 - /^<html>$/  => pattern match every `<html>` line
 - {*}         => repeat the previous pattern as many times as possible

vérifier la sortie

 $ ls
mail.txt  xx00  xx01  xx02  xx03

Si vous voulez le faire en awk

 $ awk '/<html>/{filename=NR".txt"}; {print >filename}' mail.txt
$ ls
1.txt  5.txt  9.txt  mail.txt

2voto

thiton Points 21303

Le programme csplit résout votre problème avec élégance :

 csplit '/<!DOCTYPE.*/' $FILE

2voto

Fredrik Pihl Points 20944

csplit est la meilleure solution à ce problème. J'ai juste pensé poster une solution bash pour montrer qu'il n'y a pas besoin d'aller perl sur cette tâche:

 #!/usr/bin/bash

MAIL='mail'        # path to huge mail-file

#get linenumbers for all headers
line_no=$(grep -n html $MAIL | cut -d: -f1)

read -a LINES<<< $line_no

file=0
for i in $(seq 0 2 ${#LINES[@]}); do
    start=${LINES[i]}
    end=$((${LINES[i+1]}-1))
    echo $start, $end
    sed -n "${start},${end}p" $MAIL > ${MAIL}${file}.txt
    file=$((file+1))
done

1voto

fge Points 40850

C'est faisable avec un peu de "magie" perl... Beaucoup de gens appelleraient cela moche mais voilà.

L'astuce consiste à remplacer $/ par ce que vous voulez et à lire votre entrée, en tant que telle :

 #!/usr/bin/perl -W
use strict;
my $i = 1;

$/ = <<EOF;
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head> <xmeta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type">
EOF

open INPUT, "/path/to/inputfile" or die;

while (my $mail = <INPUT>) {
    $mail = substr($mail, 0, index($mail, $/));
    open OUTPUT, ">/path/to/emailfile." . $i . ".txt" or die;
    $i++;
    print OUTPUT $mail;
    close OUTPUT;
}

edit : corrigé, j'oublie toujours que $/ est inclus dans l'entrée. De plus, le premier fichier sera toujours vide, mais il pourra ensuite être facilement manipulé.

1voto

jaypal Points 34440

Je suis d'accord avec fge. Avec perl ce serait beaucoup plus simple. Vous pouvez essayer quelque chose comme ça -

 #!/usr/bin/perl

undef $/;
$_ = <>;
$n = 0;

for $match (split(/(?=HEADER_FORMAT)/)) {
      open(O, '>mail' . ++$n);
      print O $match;
      close(O);
}

Remplacez HEADER_FORMAT par votre type d'en-tête.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X