Grep enorme numero di modelli da un enorme file


18

Ho un file che cresce di circa 200.000 righe al giorno ed è tutto formato con blocchi di tre righe come tali:

1358726575123       # key
    Joseph Muller   # name
    carpenter       # job
9973834728345
    Andres Smith
    student
7836472098652
    Mariah Anthony
    dentist

Ora, ho un altro file da cui estraggo circa 10.000 schemi chiave, come 1358726575123. Quindi eseguo un forciclo con questi modelli e devo verificarli con il primo file. Se il file non contiene tale modello, lo salvo in un terzo file per ulteriore elaborazione:

for number in $(grep -o '[0-9]\{12\}' file2); do  # finds about 10.000 keys
     if ! grep -q ^$number$ file1; then           # file1 is a huge file
         printf "$number\n" >>file3               # we'll process file3 later
     fi
done

Il codice di esempio utilizza un file enorme 10.000 volte, e eseguo questo ciclo circa una volta al minuto, per tutto il giorno .

Dato che l'enorme file continua a crescere, cosa posso fare per rendere tutto più veloce e risparmiare un po 'di CPU? Mi chiedo se l'ordinamento del file in qualche modo tramite la sua chiave (se sì, come?) O l'utilizzo di un db invece del semplice testo aiuterebbe ...


Risposte:


11

Questa risposta si basa sulla awkrisposta pubblicata da potong ..
È due volte più veloce del commmetodo (sul mio sistema), per gli stessi 6 milioni di righe nel file principale e 10 mila chiavi ... (ora aggiornato per usare FNR, NR)

Sebbene awksia più veloce del tuo sistema attuale e fornirà a te e al tuo computer un po 'di respiro, tieni presente che quando l'elaborazione dei dati è intensa come hai descritto, otterrai i migliori risultati complessivi passando a un database dedicato; per esempio. SQlite, MySQL ...


awk '{ if (/^[^0-9]/) { next }              # Skip lines which do not hold key values
       if (FNR==NR) { main[$0]=1 }          # Process keys from file "mainfile"
       else if (main[$0]==0) { keys[$0]=1 } # Process keys from file "keys"
     } END { for(key in keys) print key }' \
       "mainfile" "keys" >"keys.not-in-main"

# For 6 million lines in "mainfile" and 10 thousand keys in "keys"

# The awk  method
# time:
#   real    0m14.495s
#   user    0m14.457s
#   sys     0m0.044s

# The comm  method
# time:
#   real    0m27.976s
#   user    0m28.046s
#   sys     0m0.104s


Questo è veloce, ma non capisco molto di awk: come dovrebbero apparire i nomi dei file? Ho provato file1 -> mainfilee file2 -> keyscon gawk e mawk, e produce chiavi sbagliate.
Teresa e Junior,

file1 ha chiavi, nomi e lavori.
Teresa e Junior,

'mainfile' è il file di grandi dimensioni (con chiavi, nomi e lavori). L'ho appena chiamato "mainfile" perché ho continuato a confondere quale file era quale (file1 vs file2) .. 'keys' contiene solo le 10.000 chiavi o comunque molte .. Per la tua situazione NON reindirizzare nulla. .. basta usare file1 EOF file2 Sono i nomi dei tuoi file .. "EOF" è un file a 1 riga creato dallo script per indicare la fine del primo file (file di dati principale) e l'inizio del secondo file ( chiavi). awkti permettono di leggere in una serie di file .. In questo caso quella serie contiene 3 file. L'output va astdout
Peter.O

Questo script stamperà tutte le chiavi che sono presenti in mainfile, E sarà anche stampare tutte le chiavi dal keysfile di che sono non in mainfile... Questo è probabilmente ciò che sta accadendo ... (mi guarderò un po 'più in esso ...
Peter.O

Grazie, @ Peter.O! Dal momento che i file sono riservati, sto provando a creare file di esempio $RANDOMper il caricamento.
Teresa e Junior,

16

Il problema, ovviamente, è che esegui grep sul file di grandi dimensioni 10.000 volte. Dovresti leggere entrambi i file una sola volta. Se vuoi rimanere fuori dai linguaggi di scripting, puoi farlo in questo modo:

  1. Estrarre tutti i numeri dal file 1 e ordinarli
  2. Estrai tutti i numeri dal file 2 e ordinali
  3. Esegui commsugli elenchi ordinati per ottenere ciò che è solo sul secondo elenco

Qualcosa come questo:

$ grep -o '^[0-9]\{12\}$' file1 | sort -u -o file1.sorted
$ grep -o  '[0-9]\{12\}'  file2 | sort -u -o file2.sorted
$ comm -13 file1.sorted file2.sorted > file3

Vedere man comm.

Se potessi troncare il file di grandi dimensioni ogni giorno (come un file di registro) potresti conservare una cache di numeri ordinati e non dovrai analizzarlo ogni volta.


1
! Neat 2 secondi (su unità non particolarmente veloci) con 200.000 voci di linee casuali nel file principale (ovvero 600.000 linee) e 143.000 chiavi casuali (è così che sono finiti i miei dati di test) ... testato e funziona (ma sapevi che: ) ... Mi chiedo per {12}.. L'OP ha usato 12, ma i tasti di esempio sono 13 lunghi ...
Peter.O

2
Solo una piccola nota, puoi farlo senza occuparti di file temporanei usando <(grep...sort)dove sono i nomi dei file.
Kevin,

Grazie, ma il grepping e l'ordinamento dei file richiede molto più tempo del mio ciclo precedente (+ 2min.).
Teresa e Junior,

@Teresa e Junior. Quanto è grande il tuo file principale? ... Hai detto che cresce a 200.000 righe al giorno, ma non quanto è grande ... Per ridurre la quantità di dati che devi elaborare, puoi leggere solo le 200.000 righe dei giorni attuali prendendo nota di l'ultimo numero di riga elaborato (ieri) e utilizzato tail -n +$linenumper produrre solo i dati più recenti. In questo modo elaborerai solo circa 200.000 righe ogni giorno .. L'ho appena testato con 6 milioni di righe nel file principale e 10 mila chiavi ... tempo : real 0m0.016s, user 0m0.008s, sys 0m0.008s
Peter.O,

Sono davvero abbastanza perplesso / curioso di come puoi grep il tuo file principale 10.000 volte e trovarlo più veloce di questo metodo che lo greps solo una volta (e una volta per il file molto più piccolo1 ) ... Anche se il tuo ordinamento richiede più tempo del mio prova, non riesco proprio a pensare all'idea che la lettura di un file di grandi dimensioni che molte volte non superi un singolo ordinamento (nel tempo)
Peter.O

8

Sì, sicuramente utilizzare un database. Sono fatti esattamente per compiti come questo.


Grazie! Non ho molta esperienza con i database. Quale database mi consigliate? Ho installato MySQL e il comando sqlite3.
Teresa e Junior,

1
Stanno bene entrambi, sqlite è più semplice perché è fondamentalmente solo un file e un'API SQL per accedervi. Con MySQL è necessario configurare un server MySQL per poterlo utilizzare. Anche se non è molto difficile, sqlite potrebbe essere il migliore per cominciare.
Mika Fischer,

3

Questo potrebbe funzionare per te:

 awk '/^[0-9]/{a[$0]++}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

MODIFICARE:

Lo script modificato per consentire duplicati e chiavi sconosciute in entrambi i file, produce ancora chiavi dal primo file non presenti nel secondo:

 awk '/^[0-9]/{if(FNR==NR){a[$0]=1;next};if($0 in a){a[$0]=2}}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

Ciò mancherà le nuove chiavi che si verificano più di una volta nel file principale (e del resto, che si verificano più di una volta nel file delle chiavi) Sembra che l'incremento del conteggio di array del file principale non sia superiore a 1, o qualche soluzione alternativa equivalente (+1 perché è abbastanza vicino al segno)
Peter.O

1
Ho provato con gawk e mawk, e produce chiavi sbagliate ...
Teresa e Junior

@ Peter.OI supponevo che il file principale avesse chiavi uniche e che il file 2 fosse un sottoinsieme del file principale.
potong

@potong Il secondo funziona bene e molto velocemente! Grazie!
Teresa e Junior,

@Teresa e Junior Sei sicuro che funzioni correttamente? .. Usando i dati di test che hai fornito , che dovrebbe generare 5000 chiavi, quando lo eseguo , produce 136703 chiavi, proprio come ho ottenuto fino a quando ho finalmente capito quali erano i tuoi requisiti ... @potong Certo! FNR == NR (non l'ho mai usato prima :)
Peter.O

2

Con così tanti dati, dovresti davvero passare a un database. Nel frattempo, una cosa che devi fare per avvicinarti a prestazioni decenti è non cercare file1separatamente ogni tasto. Esegui un singolo grepper estrarre contemporaneamente tutte le chiavi non escluse. Dal momento che greprestituisce anche righe che non contengono una chiave, filtrale via.

grep -o '[0-9]\{12\}' file2 |
grep -Fxv -f - file1 |
grep -vx '[0-9]\{12\}' >file3

( -Fxsignifica cercare intere righe, letteralmente. -f -significa leggere un elenco di schemi dall'input standard.)


A meno che non mi sbagli, questo non risolve il problema dell'archiviazione delle chiavi che non sono nel file di grandi dimensioni, ma memorizzerà le chiavi in ​​esso contenute.
Kevin,

@Kevin esattamente, e questo mi ha costretto a usare il loop.
Teresa e Junior,

@TeresaeJunior: l'aggiunta di -v( -Fxv) potrebbe occuparsene.
In pausa fino a nuovo avviso.

@DennisWilliamson Quello sceglierebbe tutte le righe nel file grande che non corrispondono a nessuna nel file chiave, inclusi nomi, lavori, ecc.
Kevin

@Kevin Grazie, avrei letto male la domanda. Ho aggiunto un filtro per le linee non chiave, anche se ora la mia preferenza va all'utilizzocomm .
Gilles 'SO- smetti di essere malvagio' il

2

Mi permetta di rinforzare ciò che gli altri hanno detto: "Portati in un database!"

Esistono binari MySQL disponibili gratuitamente per la maggior parte delle piattaforme.

Perché non SQLite? È basato sulla memoria, carica un file flat quando lo avvii, quindi chiudendolo quando hai finito. Ciò significa che se il computer si arresta in modo anomalo o il processo SQLite si interrompe, lo stesso vale per tutti i dati.

Il tuo problema sembra solo un paio di righe di SQL e verrà eseguito in millisecondi!

Dopo aver installato MySQL (che consiglio su altre scelte), sborserei $ 40 per il Cookbook SQL O'Reilly di Anthony Molinaro, che ha molti schemi di problemi, a partire da SELECT * FROM tablequery semplici e passando attraverso aggregati e join multipli.


Sì, inizierò a migrare i miei dati su SQL tra qualche giorno, grazie! Gli script Awk mi hanno aiutato molto fino a quando non ho fatto tutto!
Teresa e Junior,

1

Non sono sicuro se questo è l'output esatto che stai cercando, ma probabilmente il modo più semplice è:

grep -o '[0-9]\{12\}' file2 | sed 's/.*/^&$/' > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

Puoi anche usare:

sed -ne '/.*\([0-9]\{12\}.*/^\1$/p' file2 > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

Ognuno di questi crea un file di pattern temporaneo che viene utilizzato per estrarre i numeri dal file di grandi dimensioni ( file1).


Credo che anche questo trovi i numeri nel file di grandi dimensioni, non quelli che non lo sono.
Kevin,

Esatto, non ho visto il "!" nell'OP. Devo solo usare grep -vfinvece di grep -f.
Arcege,

2
No @arcege, grep -vf non visualizzerà chiavi non corrispondenti, mostrerà tutto, inclusi nomi e lavori.
Teresa e Junior,

1

Sono pienamente d'accordo che tu ottenga un database (MySQL è abbastanza facile da usare). Fino a quando non riuscirai a farlo funzionare, mi piace la commsoluzione di Angus , ma così tante persone ci stanno provando grepe sbagliano che pensavo di mostrare il (o almeno un) modo corretto di farlo grep.

grep -o '[0-9]\{12\}' keyfile | grep -v -f <(grep -o '^[0-9]\{12\}' bigfile) 

Il primo grepottiene le chiavi. Il terzo grep(nel <(...)) prende tutte le chiavi usate nel file grande e lo <(...)passa come un file come argomento a -fnel secondo grep. Ciò fa sì che il secondo greplo utilizzi come un elenco di righe corrispondenti. Quindi utilizza questo per abbinare il suo input (l'elenco delle chiavi) dalla pipe (prima grep) e stampa tutte le chiavi estratte dal file delle chiavi e non ( -v) il file grande.

Ovviamente puoi farlo con i file temporanei di cui devi tenere traccia e ricordare di eliminare:

grep -o '[0-9]\{12\}'  keyfile >allkeys
grep -o '^[0-9]\{12\}' bigfile >usedkeys
grep -v -f usedkeys allkeys

Questo stampa tutte le linee in allkeyscui non appaiono usedkeys.


Purtroppo è lento e dopo 40 secondi viene visualizzato un errore di memoria:grep: Memory exhausted
Peter

@ Peter.O Ma è corretto. Comunque, ecco perché suggerirei un database o comm, in questo ordine.
Kevin,

Sì, funziona, ma è molto più lento del loop.
Teresa e Junior,

1

Il file di chiavi non cambia? Quindi dovresti evitare di cercare più volte le voci precedenti.

Con tail -fte puoi ottenere l'output di un file in crescita.

tail -f growingfile | grep -f keyfile 

grep -f legge i pattern da un file, una riga come pattern.


Sarebbe buono, ma il file chiave è sempre diverso.
Teresa e Junior,

1

Non avevo intenzione di pubblicare la mia risposta perché pensavo che una tale quantità di dati non dovesse essere elaborata con uno script di shell e la risposta giusta per utilizzare un database era già stata fornita. Ma da ora ci sono altri 7 approcci ...

Legge il primo file in memoria, quindi greps il secondo file per i numeri e controlla se i valori sono memorizzati. Dovrebbe essere più veloce di più greps, se hai memoria sufficiente per caricare l'intero file, cioè.

declare -a record
while read key
do
    read name
    read job
    record[$key]="$name:$job"
done < file1

for number in $(grep -o '[0-9]\{12\}' file2)
do
    [[ -n ${mylist[$number]} ]] || echo $number >> file3
done

Ho abbastanza memoria, ma ho trovato questo ancora più lento. Grazie comunque!
Teresa e Junior,

1

Sono d'accordo con @ jan-steinman che dovresti usare un database per questo tipo di attività. Ci sono molti modi per hackerare insieme una soluzione con uno script di shell come mostrano le altre risposte, ma farlo in questo modo porterà a molta sofferenza se intendi utilizzare e mantenere il codice per un periodo di tempo superiore a solo un progetto usa e getta di un giorno.

Supponendo che tu sia su una scatola Linux, molto probabilmente hai Python installato per impostazione predefinita che include la libreria sqlite3 a partire da Python v2.5. Puoi controllare la tua versione di Python con:

% python -V
Python 2.7.2+

Consiglio di utilizzare la libreria sqlite3 perché è una soluzione basata su file semplice per tutte le piattaforme (incluso all'interno del browser Web!) E non richiede l'installazione di un server. Essenzialmente configurazione zero e manutenzione zero.

Di seguito è riportato un semplice script Python che analizzerà il formato file che hai fornito come esempio e quindi esegue una semplice query "seleziona tutto" e restituisce tutto ciò che è archiviato nel database.

#!/usr/bin/env python

import sqlite3
import sys

dbname = '/tmp/simple.db'
filename = '/tmp/input.txt'
with sqlite3.connect(dbname) as conn:
    conn.execute('''create table if not exists people (key integer primary key, name text, job text)''')
    with open(filename) as f:
        for key in f:
            key = key.strip()
            name = f.next().strip()
            job = f.next().strip()
            try:
                conn.execute('''insert into people values (?,?,?)''', (key, name, job))
            except sqlite3.IntegrityError:
                sys.stderr.write('record already exists: %s, %s, %s\n' % (key, name, job))
    cur = conn.cursor()

    # get all people
    cur.execute('''select * from people''')
    for row in cur:
        print row

    # get just two specific people
    person_list = [1358726575123, 9973834728345]
    cur.execute('''select * from people where key in (?,?)''', person_list)
    for row in cur:
        print row

    # a more general way to get however many people are in the list
    person_list = [1358726575123, 9973834728345]
    template = ','.join(['?'] * len(person_list))
    cur.execute('''select * from people where key in (%s)''' % (template), person_list)
    for row in cur:
        print row

Sì, questo significa che dovrai imparare un po 'di SQL , ma ne varrà la pena nel lungo periodo. Inoltre, invece di analizzare i file di registro, forse potresti scrivere i dati direttamente nel tuo database sqlite.


Grazie per lo script Python! Penso che /usr/bin/sqlite3funzioni allo stesso modo per gli script di shell ( pacchetti.debian.org/squeeze/sqlite3 ), anche se non l'ho mai usato.
Teresa e Junior,

Sì, puoi usarlo /usr/bin/sqlite3con gli script di shell, tuttavia ti consiglio di evitare gli script di shell ad eccezione dei semplici programmi usa e getta e invece utilizzare un linguaggio come Python che ha una migliore gestione degli errori ed è più facile da mantenere e crescere.
aculich,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.