2011-02-10 10 views
11

Ciao ragazzi Spero che il soggetto sia abbastanza chiaro, non ho trovato nulla di specifico su questo nel cestino precedentemente chiesto. Ho provato a implementarlo in Perl o Python, ma penso che potrei provarci troppo.Divisione di un file txt di grandi dimensioni in 200 file txt più piccoli in un'espressione regolare utilizzando lo script di shell in BASH

C'è un semplice comando shell/pipeline che dividerà il mio file .txt da 4mb in file .txt separati, basato su un'espressione regolare iniziale e finale?

Fornisco un breve esempio del file qui sotto .. così puoi vedere che ogni "storia" inizia con la frase "X di XXX DOCUMENTI", che potrebbe essere usata per dividere il file.

Penso che dovrebbe essere facile e sarei sorpreso se bash non fosse in grado di farlo - più veloce di Perl/Py.

Eccolo:

      1 of 999 DOCUMENTS 


       Copyright 2011 Virginian-Pilot Companies LLC 
          All Rights Reserved 
        The Virginian-Pilot(Norfolk, VA.) 

... 



          3 of 999 DOCUMENTS 


        Copyright 2011 Canwest News Service 
          All Rights Reserved 
          Canwest News Service 

... 

Grazie in anticipo per il vostro aiuto.

Ross

+1

è questo testo di esempio necessario? – jakev

+1

Modifica e rimuovi circa il 95% del testo nella tua domanda. –

+0

possibile duplicato di [Dividi un file in più file in base al delimitatore] (http://stackoverflow.com/questions/11313852/split-one-file-into-multiple-files-based-on-delimiter) – tripleee

risposta

22
awk '/[0-9]+ of [0-9]+ DOCUMENTS/{g++} { print $0 > g".txt"}' file 

utenti OSX avrà bisogno gawk, come il comando incorporato awk produrrà un errore come awk: illegal statement at source line 1

Rubino (1.9+)

#!/usr/bin/env ruby 
g=1 
f=File.open(g.to_s + ".txt","w") 
open("file").each do |line| 
    if line[/\d+ of \d+ DOCUMENTS/] 
    f.close 
    g+=1 
    f=File.open(g.to_s + ".txt","w") 
    end 
    f.print line 
end 
+0

OH e abbiamo un vincitore .... velocità * E * eleganza Ho trascorso un'estate davvero umida nel 1997 con il libro O'Reilly sed/awk. Vorrei poter ricordare tutto questo ora. Io * andrò e prenderò il tmrw. ** GRAZIE ** – rosser

+1

Questa soluzione mette la riga corrispondente nel nuovo file, che risponde alla domanda. Ma se, come me, vuoi mettere la riga corrispondente nel vecchio file prima di iniziarne una nuova, dovresti fare questo: 'awk '{print $ 0> n" .txt "}/text per abbinare/{n ++} ' – indiv

+1

Nota: su Mac OS X è necessario' gawk' da es. MacPort per farlo funzionare –

0

regex per abbinare "X di documenti XXX" è
\ d {1,3} di \ d {1,3) DOCUMENTI

linea di lettura per riga e iniziare a scrivere nuovi file su regex match dovrebbe andare bene.

-1

testato:

base=outputfile 
start=1 
pattern='^[[:blank:]]*[[:digit:]]+ OF [[:digit:]]+ DOCUMENTS[[:blank:]]*$ 

while read -r line 
do 
    if [[ $line =~ $pattern ]] 
    then 
     ((start++)) 
     printf -v filecount '%4d' $start 
     >"$base$filecount" # create an empty file named like foo0001 
    fi 
    echo "$line" >> "$base$filecount" 
done 
+0

A proposito , quanto sopra è puro Bash. Inoltre, sono sicuro che Python o Perl sarebbero molto più veloci. –

+1

Puoi farlo con csplit? csplit -k -z --digits = 3 --suffix = '% d.TXT' --prefix = FILE *.TXT/'SPLITONTHIS' – rosser

+0

@rosser - questo è un candidato per split, non so csplit anche se – sln

1

Come è difficile hai provato in Perl?

Modifica Ecco un metodo più veloce. Divide il file quindi stampa i file delle parti.

use strict; 
use warnings; 

my $count = 1; 

open (my $file, '<', 'source.txt') or die "Can't open source.txt: $!"; 

for (split /(?=^.*\d+[^\S\n]*of[^\S\n]*\d+[^\S\n]*DOCUMENTS)/m, join('',<$file>)) 
{ 
    if (s/^.*(\d+)\s*of\s*\d+\s*DOCUMENTS.*(\n|$)//m) 
    { 
     open (my $part, '>', "Part$1_$count.txt") 
      or die "Can't open Part$1_$count for output: $!"; 
     print $part $_; 
     close ($part); 
     $count++; 
    } 
} 
close ($file); 

Questo è il metodo integrale:

use strict; 
use warnings; 

open (my $masterfile, '<', 'yourfilename.txt') or die "Can't open yourfilename.txt: $!"; 

my $count = 1; 
my $fh; 

while (<$masterfile>) { 
    if (/(?<!\d)(\d+)\s*of\s*\d+\s*DOCUMENTS/) { 
     defined $fh and close ($fh); 
     open ($fh, '>', "Part$1_$count.txt") or die "Can't open Part$1_$count for output: $!"; 
     $count++; 
     next; 
    } 
    defined $fh and print $fh $_; 
} 
defined $fh and close ($fh); 
close ($masterfile); 
+0

'$ count' non è definito. Sospetto che tu intendessi "$ cnt". Inoltre, la prima volta che si esegue il ciclo '$ fh' non è definito, quindi si otterrà un errore/avvertimento' Non si può usare un valore non definito come simbolo di riferimento 'quando si tenta di chiudere '$ fh'. – CanSpice

+0

@ Can Spice, come va ora? – sln

+1

Adesso è meglio! – CanSpice

9

Come suggerito in altre soluzioni, è possibile utilizzare csplit per questo:

csplit csplit.test '/^\.\.\./' '{*}' && sed -i '/^\.\.\./d' xx* 

non ho trovato un modo migliore per sbarazzarsi del separatore che ricorda nelle file divisi.

+0

Non riesco a provare ora perché su Windows, ma la pagina man di csplit sembra suggerire l'uso di% REGEX% invece di/REGEX/per quello: /REGEXP/[OFFSET] copia fino a ma non inclusa una linea corrispondente % REGEXP% [OFFSET] salta a, ma non include una linea corrispondente – Spikolynn

Problemi correlati