2011-12-17 27 views
33

Tengo un volcado de correo electrónico de alrededor de 400mb. Quiero dividir esto en archivos .txt, que consiste en un correo en cada archivo. Cada correo electrónico comienza con el encabezado HTML estándar que especifica el doctype.División de un archivo en Linux basado en el contenido

Esto significa que tendré que dividir mis archivos en función de dicho encabezado. ¿Cómo lo hago en Linux?

+0

¿Realmente es un volcado de correo electrónico? ¿Quiere decir que no tiene encabezados de correo? ¿Y a qué llama el "encabezado HTML estándar que especifica el doctype"? – fge

+0

" Esto es seguido por todo el correo electrónico! – Greenhorn

Respuesta

54

Si usted tiene un mail.txt

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

<html> 
    mail B 
</html> 

<html> 
    mail C 
</html> 

plazo csplit para dividir por <html>

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

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

Salida de control

$ ls 
mail.txt xx00 xx01 xx02 xx03 

Si desea hacerlo en awk

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

temo! hice lo mismo y realizó un $ ls mail.txt xx00 y obviamente correo .txt era el mismo que xx00 ¿Alguna solución? – Greenhorn

+0

@Ramprakash La versión de My 'csplit' es' 8.5'. Quizás el tuyo no tenga el '{*}' que patrón de repetición. Por favor revisa la página de manual. Solo agrego la solución 'awk'. Puedes probarlo. – kev

+0

Awk trabajado :) ¡Muchas gracias! – Greenhorn

1

Es factible con algo de perl "magia" ... Mucha gente llamaría a esto feo, pero aquí va.

El truco consiste en reemplazar $/ con lo que quieres y leer su entrada, tales como:

#!/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; 
} 

edición: fijo, siempre se me olvida que $/ está incluido en la entrada. Además, el primer archivo siempre estará vacío, pero luego se puede manejar fácilmente.

1

Estoy de acuerdo con fge. Con perl sería mucho más simple. Usted puede intentar algo como esto -

#!/usr/bin/perl 

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

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

Reemplazar HEADER_FORMAT con su tipo de cabecera.

+0

Sí, un lookahead positivo funcionaría muy bien, especialmente porque aquí el encabezado no contiene ningún metacarácter. Incluso podría usar 'qr //' para construir la expresión regular dividida. – fge

4

El programa csplit resuelve su problema con elegancia:

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

los argumentos están en el orden equivocado y le falta la repetición de hacer realidad la forma prevista. – qwertzguy

2

csplit es la mejor solución para este problema. Solo pensé en publicar una solución bash para mostrar que no hay necesidad de ir a Perl en esta tarea:

#!/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 
Cuestiones relacionadas