2009-05-11 11 views
8

Devo gestire file di testo in formato molto grande (oltre 10 gigabyte, sì, so che dipende da cosa dovremmo chiamare grande), con linee molto lunghe.ottimizzazione sed (grande modifica di file basata su set di dati più piccoli)

L'attività più recente riguarda alcune modifiche di riga in base ai dati di un altro file.

Il file di dati (che dovrebbe essere modificato) contiene 1500000 linee, ognuna delle quali è ad es. Lungo 800 caratteri. Ogni riga è univoca e contiene un solo numero identificativo, ogni numero di identità è univoco)

Il file di modifica è ad esempio. 1800 righe, contiene un numero identificativo e una quantità e una data che dovrebbero essere modificate nel file di dati.

Ho appena trasformato (con Vim regex) il file di modifica in sed, ma è molto inefficiente.

Diciamo che ho una linea come questa nel file di dati:

(some 500 character)id_number(some 300 character) 

E ho bisogno di modificare i dati nella parte 300 char.

Sulla base del file di modificatore, ho messo a punto linee sed come questo:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/ 

così ho 1800 linee come questo.

Ma so, che anche su un server molto veloce, se faccio un

sed -i.bak -f modifier.sed data.file 

'molto lento, perché deve leggere ogni modello x ogni linea.

Non c'è un modo migliore?

Nota: Non sono un programmatore, non ho mai imparato (a scuola) sugli algoritmi. Posso usare awk, sed, una versione obsoleta di perl sul server.

+1

Qual è la versione per il Perl? –

+0

perl 5.8.6 i586-linux-thread-multi –

+1

Quella * è * una versione perl obsoleta, ma sospetto che non sia così grave come la gente avrebbe concluso dalla nota;) – user55400

risposta

6

mio suggerito approcci (in ordine di preferibilmente) sarebbe quello di trattare tali dati come:

  1. Un database (anche un semplice SQLite-based DB con un indice si esibirà molto meglio di sed/awk su un file da 10 GB)
  2. un file flat che contiene record di fisso lunghezze
  3. un file flat che contiene record di variabile lunghezza

Utilizzando un database prende c sono tutti quei piccoli dettagli che rallentano l'elaborazione dei file di testo (trovare il record che ti interessa, modificare i dati, archiviarli nel DB). Dai un'occhiata a DBD :: SQLite nel caso di Perl.

Se si desidera conservare file flat, è necessario mantenere manualmente un indice accanto al file di grandi dimensioni in modo da poter controllare più facilmente i numeri dei record da manipolare. O, meglio ancora, forse i tuoi numeri di identificazione sono i tuoi numeri di record?

Se si dispone di lunghezze di record variabili, suggerirei di convertire in lunghezze di record fissi (poiché sembra che solo il proprio ID sia di lunghezza variabile). Se non puoi farlo, forse nessun dato esistente non si muoverà mai nel file? Quindi è possibile mantenere quell'indice menzionato in precedenza e aggiungere nuove voci secondo necessità, con la differenza che al posto dell'indice che punta al numero di record, ora si punta alla posizione assoluta nel file.

+0

Vado con il metodo DB. Oracle è disponibile. Attualmente sqlldr-ing ... –

+1

La soluzione DB (con sqlldr, sqlplus) è appena terminata, mentre il sed ancora è in esecuzione al 7% ... –

3

Ti suggerisco un programma scritto in Perl (dato che non sono un guru di sed/awk e non so di cosa siano esattamente capaci).

L'"algoritmo" è semplice: è necessario costruire, prima di tutto, una hashmap che potrebbe fornire la nuova stringa di dati da applicare per ciascun ID. Questo si ottiene leggendo il file di modifica, naturalmente.

Una volta popolata questa mappa, è possibile sfogliare ogni riga del file di dati, leggere l'ID nel mezzo della riga e generare la nuova riga come descritto sopra.

Anche io non sono un guru di Perl, ma penso che il programma sia abbastanza semplice.Se hai bisogno di aiuto per scrivere, chiedere esso :-)

+0

Sembra una buona soluzione, purché l'ID di una linea può essere estratto con uno sforzo ragionevole - che non è chiaro dalla domanda, ma una buona ipotesi, imho. – user55400

2

Con perl è necessario utilizzare substr per ottenere numero_um, specialmente se numero_um ha larghezza costante.

my $id_number=substr($str, 500, id_number_length); 

Dopo che se $ id_number è in campo, si dovrebbe usare substr per sostituire il testo rimanente.

substr($str, -300,300, $new_text); 

Le espressioni regolari di Perl sono molto veloci, ma non in questo caso.

0

Si dovrebbe quasi certamente utilizzare un database, come MikeyB suggested.

Se non si desidera utilizzare un database per qualche motivo, se l'elenco delle modifiche si adatta alla memoria (come avviene attualmente a 1800 righe), il metodo più efficiente è una tabella hash popolata con le modifiche come suggerito da yves Baumes.

Se si arriva al punto in cui anche l'elenco delle modifiche diventa enorme, è necessario risolvere entrambi i file dal loro ID e quindi eseguire un lista unire - in pratica:

  1. Confrontare l'ID a il "top" del file di input con l'ID al "top" dei file delle modifiche
  2. Regolare il record di conseguenza se corrispondono
  3. scriverlo fuori
  4. Eliminare la linea "top" da qualsiasi file di aveva il (in ordine alfabetico o numericamente) ID più basso e leggere un'altra riga da quel file
  5. Goto 1.

Dietro le quinte, un database utilizzerà quasi certamente un elenco di unione se si esegue questa modifica utilizzando un singolo comando SQL UPDATE.

0

Buon affare sulla decisione sqlloader o datadump. Questa è la strada da percorrere.

+0

Questo dovrebbe essere stato pubblicato come commento. – Viet

1

Il mio suggerimento è, non utilizzare il database. Lo script perl ben scritto supererà il database in ordine di grandezza in questo tipo di compito. Fidati di me, ho molte esperienze pratiche con esso. Non avrai importato i dati nel database quando perl sarà finito.

Quando si scrivono 1500000 righe con 800 caratteri, mi sembra 1,2 GB. Se avrai un disco molto lento (30 MB/s) lo leggerete in 40 secondi. Con meglio 50 -> 24s, 100 -> 12s e così via. Ma la ricerca di hash perl (come db join) sulla velocità della CPU a 2GHz è superiore a 5Mlookups/s. Significa che il tuo lavoro con CPU vincolata sarà in secondi e il tuo lavoro legato all'IO sarà in decine di secondi. Se è veramente 10 GB cambieranno i numeri, ma la proporzione è la stessa.

Non è stato specificato se la modifica dei dati cambia dimensione o meno (se la modifica può essere eseguita in posizione), pertanto non la assumeremo e funzionerà come filtro. Non hai specificato quale formato del tuo "file modificatore" e quale tipo di modifica. Si supponga che è separata da scheda qualcosa come:

<id><tab><position_after_id><tab><amount><tab><data> 

leggeremo i dati da stdin e scrivere su stdout e script può essere qualcosa di simile:

my $modifier_filename = 'modifier_file.txt'; 

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!"; 
my %modifications; 
while (<$mf>) { 
    chomp; 
    my ($id, $position, $amount, $data) = split /\t/; 
    $modifications{$id} = [$position, $amount, $data]; 
} 
close $mf; 

# make matching regexp (use quotemeta to prevent regexp meaningful characters) 
my $id_regexp = join '|', map quotemeta, keys %modifications; 
$id_regexp = qr/($id_regexp)/;  # compile regexp 

while (<>) { 
    next unless m/$id_regexp/; 
    next unless $modifications{$1}; 
    my ($position, $amount, $data) = @{$modifications{$1}}; 
    substr $_, $+[1] + $position, $amount, $data; 
} 
continue { print } 

sul mio computer portatile ci vuole circa mezzo minuto per 1,5 milioni di righe, 1800 ID di ricerca, 1,2 GB di dati. Per 10 GB non dovrebbe essere più di 5 minuti. È ragionevole veloce per te?

Se si inizia a pensare che non siete tenuti IO (per esempio se utilizzare alcuni NAS), ma CPU bound si può sacrificare un po 'la leggibilità e cambiare a questo:

my $mod; 
while (<>) { 
    next unless m/$id_regexp/; 
    $mod = $modifications{$1}; 
    next unless $mod; 
    substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2]; 
} 
continue { print } 
+0

Anche se il mio compito è terminato, riproverò la soluzione anche perché Oracle non è sempre disponibile. Ad ogni modo, grazie per il tuo aiuto. –

Problemi correlati