Büyük dosyadan çok sayıda desen Grep


18

Günde yaklaşık 200.000 satır büyüyen bir dosyam var ve hepsi üç satırlık bloklardan oluşuyor:

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

Şimdi, yaklaşık 10.000 anahtar modelini çıkardığım başka bir dosyam var 1358726575123. Sonra birfor bu desenler ile döngü ve onları ilk dosyaya karşı kontrol etmek zorunda. Dosya böyle bir desen içermiyorsa, daha sonraki işlemler için kalıbı üçüncü bir dosyaya kaydederim:

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

Örnek kod 10.000 kez büyük bir dosya greps ve bu döngü hakkında çalıştırmak bütün gün boyunca dakikada kez .

Büyük dosya büyümeye devam ettiğinden, bunları daha hızlı hale getirmek ve biraz CPU kaydetmek için ne yapabilirim? Ben bir şekilde dosyayı anahtarı ile sıralama (eğer öyleyse, nasıl?) Veya düz metin yerine bir db kullanarak yardımcı olacağını merak ediyorum ...


Yanıtlar:


11

Bu yanıt potongawk tarafından gönderilen cevaba dayanmaktadır .. Ana dosyada aynı 6 milyon satır için (sistemimde) yöntemden
iki kat daha hızlıdır vecomm 10 bin anahtar ... (şimdi FNR kullanmak için güncellendi, NR)

awkMevcut sisteminizden daha hızlı olsa da ve size ve bilgisayarlarınıza biraz nefes alan verecek olsa da, veri işleme tanımladığınız kadar yoğun olduğunda, özel bir veritabanına geçerek en iyi genel sonuçları elde edeceğinizi unutmayın; Örneğin. 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


Bu hızlı, ama awk fazla anlamıyorum: dosya adları neye benzemeli? Ben gawk ve mawk ile denedim file1 -> mainfileve file2 -> keysyanlış anahtarlar çıktı.
Teresa e Junior

dosya1'in anahtarları, adları ve işleri var.
Teresa e Junior

'mainfile' büyük dosyadır (anahtarlar, isimler ve işlerle birlikte). Ben sadece "mainfile" olarak adlandırdım çünkü hangi dosyanın (file1 vs file2) olduğu kadar karışmaya devam ettim ... 'anahtarlar' sadece 10 bin ya da birçok anahtar içeriyor ... Situaton için herhangi bir yönlendirme yapmayın . .. sadece dosya1 kullanın EOF dosya2 Onlar dosyalarınızın isimleri .. "EOF" komut dosyası tarafından ilk dosyanın sonunu (ana veri dosyası) ve ikinci dosyanın başlangıcını göstermek için 1 satırlık bir dosya creadte ( . tuşları) awkEğer dizi sahip olduğunu, bu durumda .. dosyaların bir seride okumasına izin 3 içine dosyalar çıktı gider.stdout
Peter.O

Bu komut dosyası mevcut olan herhangi bir anahtar yazdırır mainfile, VE aynı zamanda herhangi bir anahtarlarını yazdırır keysolan dosyanın DEĞİL içinde mainfile... Bu (ı ... ayrıntılı bir şekilde yardımcı biraz bakacağız ... neler olduğunu muhtemelen
Peter.O

Teşekkürler @ Peter.O! Dosyalar gizli olduğundan, $RANDOMyükleme için örnek dosyalar oluşturmaya çalışıyorum .
Teresa e Junior

16

Sorun, elbette, büyük dosyada 10.000 kez grep çalıştırmanızdır. Her iki dosyayı da yalnızca bir kez okumalısınız. Komut dosyası dillerinin dışında kalmak istiyorsanız, bunu şu şekilde yapabilirsiniz:

  1. Dosya 1'deki tüm sayıları ayıkla ve sırala
  2. Dosya 2'deki tüm sayıları ayıkla ve sırala
  3. commYalnızca ikinci listede olanı almak için sıralı listelerde çalıştırın

Bunun gibi bir şey:

$ 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

Bkz man comm.

Büyük dosyayı her gün (bir günlük dosyası gibi) kesebilirseniz, sıralanan numaraların önbelleğini tutabilirsiniz ve her seferinde onu ayrıştırmanız gerekmez.


1
Temiz! 2 saniye (özellikle hızlı sürücülerde değil), ana dosyadaki 200.000 rasgele satır girişi (yani 600.000 satır) ve 143.000 rasgele anahtar (test verilerim böyle bitti) ... test edildi ve işe yaradı (ama şunu biliyordunuz: ) ... Merak ediyorum {12}.. OP 12 kullandı, ama örnek anahtarlar 13 uzun ...
Peter.O

2
Sadece küçük bir not, <(grep...sort)dosya adlarının bulunduğu yeri kullanarak geçici dosyalarla uğraşmadan yapabilirsiniz .
Kevin

Teşekkür ederim, ancak dosyaları açmak ve sıralamak önceki döngümden (+ 2dk) çok daha uzun sürüyor.
Teresa e Junior

@Teresa e Junior. Ana dosyanız ne kadar büyük? ... Günde 200.000 satırda büyüdüğünü, ancak ne kadar büyük olmadığını belirtmişsiniz ... İşleme koymanız gereken veri miktarını azaltmak için, şu anki günlerin 200.000 satırını bir not alarak okuyabilirsiniz. işlenen son satır numarası (dün) ve tail -n +$linenumyalnızca en son verileri almak için kullanılır. Bu şekilde her gün sadece yaklaşık 200.000 satır işleyeceksiniz. Sadece ana dosyada 6 milyon satır ve 10 bin anahtarla test ettim ... zaman : gerçek 0m0.016s, kullanıcı 0m0.008s, sys 0m0.008s
Peter.O

Gerçekten oldukça / size ana dosya grep nasıl merak şaşkın 10,000 kez ve daha hızlı yalnızca onu greps bu yöntemle daha bulmak bir kez (ve çok daha küçük bir kez Dosya1 ) ekleyin tür daha uzun sürer ... Bile benim test, sadece birçok kez büyük bir dosyayı okumanın tek bir türden daha fazla
ağırlamadığı fikrini aklıma getiremiyorum

8

Evet, kesinlikle bir veritabanı kullan. Tam olarak böyle işler için yapılırlar.


Teşekkürler! Veritabanları hakkında fazla deneyimim yok. Hangi veritabanını öneriyorsunuz? MySQL ve sqlite3 komutunu yükledim.
Teresa e Junior

1
Her ikisi de bunun için iyi, sqlite daha basit, çünkü temelde sadece bir dosya ve erişmek için bir SQL API. MySQL ile kullanmak için bir MySQL sunucusu kurmanız gerekir. Bu da çok zor olmasa da, sqlite en iyisi olabilir.
Mika Fischer

3

Bu sizin için işe yarayabilir:

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

DÜZENLE:

Her iki dosyada da kopyalara ve bilinmeyen anahtarlara izin vermek için değiştirilmiş komut dosyası, yine de ikinci dosyada bulunmayan ilk dosyadan anahtarlar üretir:

 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

Bu, ana dosyada bir kereden fazla meydana gelen yeni anahtarları (ve bu dosyada, anahtarlar dosyasında bir kereden fazla meydana gelen) özleyecektir. veya eşdeğer bir geçici çözüm (+1, işarete oldukça yakın olduğu için +1)
Peter.O

1
Gawk ve mawk ile denedim ve yanlış tuşlar çıktı ...
Teresa e Junior

@ Peter.OI, ana dosyanın benzersiz anahtarlara sahip olduğunu ve bu dosya 2'nin ana dosyanın bir alt kümesi olduğunu varsaymıştır.
12'de

@potong İkincisi iyi ve çok hızlı çalışıyor! Teşekkür ederim!
Teresa e Junior

@Teresa e Junior Henüz doğru çalıştığından emin misiniz? .. 5000 anahtar vermesi gereken, sağladığınız test verilerini kullanarak, çalıştırdığımda 136703 anahtar üretiyor . ... @potong Elbette! FNR == NR (Daha önce hiç kullanmadım :)
Peter.O

2

Bu kadar veriyle, gerçekten bir veritabanına geçmelisiniz. Bu arada, iyi performansa yakın bir yere ulaşmak için yapmanız gereken bir şey file1, her bir anahtar için ayrı olarak arama yapmak değildir . grepHariç tutulan tüm anahtarları bir kerede ayıklamak için bir tekli çalıştırın . Bu, grepanahtar içermeyen satırları da döndürdüğünden, bunları filtreleyin.

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

( -Fxtüm satırları tam anlamıyla aramak anlamına gelir. -f -standart girişten bir kalıp listesi okumak anlamına gelir.)


Yanılmıyorsam, bu büyük dosyada olmayan anahtarların saklanması sorununu çözmez, içindeki anahtarları saklar.
Kevin

@Kevin tam olarak, bu da beni döngüyü kullanmaya zorladı.
Teresa e Junior

@TeresaeJunior: -v( -Fxv) ekleme işlemi bununla ilgilenebilir.
sonraki duyuruya kadar duraklatıldı.

@DennisWilliamson Bu, büyük dosyadaki adlar, işler vb. Dahil olmak üzere anahtar dosyada eşleşmeyen tüm satırları seçer.
Kevin

@Kevin Teşekkürler, soruyu yanlış okudum. Tercihim artık kullanmayacomm devam etse de, anahtar olmayan satırlar için bir filtre ekledim .
Gilles 'SO- kötü olmayı bırak'

2

Başkalarının söylediklerini pekiştirmeme izin ver, "Sizi bir veritabanına alın!"

Var Çoğu platform için serbestçe kullanılabilen MySQL ikili dosyaları .

SQLite neden olmasın? Bellek tabanlıdır, başlattığınızda düz bir dosya yükler ve bitirdiğinizde kapatır. Bu, bilgisayarınız çökerse veya SQLite işlemi kaybolursa, tüm verilerin de yok olduğu anlamına gelir.

Sorununuz sadece birkaç SQL satırı gibi görünüyor ve milisaniye içinde çalışacak!

MySQL'i yükledikten sonra (diğer seçeneklere göre tavsiye ederim), AnthonyReor tarafından O'Reilly'nin SQL Yemek Kitabı için 40 $ 'lık bir kabuk ekledim SELECT * FROM table.


Evet, birkaç gün içinde verilerimi SQL'e taşımaya başlayacağım, teşekkürler! Her şey bitene kadar awk senaryoları bana çok yardımcı oldu!
Teresa e Junior

1

Bu aradığınız tam çıktı olup olmadığından emin değilim, ama muhtemelen en kolay yolu:

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

Ayrıca şunları kullanabilirsiniz:

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

Bunların her biri, büyük dosyadan ( file1) sayıları çıkarmak için kullanılan geçici bir desen dosyası oluşturur .


Bunun da büyük dosyada olmayan sayıları bulduğuna inanıyorum.
Kevin

Doğru, '!' OP. Bunun grep -vfyerine kullanmanız yeterlidir grep -f.
Arcege

2
@Arcege yok, grep -vf eşleşmeyen tuşları görüntülemez, adlar ve işler dahil her şeyi görüntüler.
Teresa e Junior

1

Bir veritabanı almayı tamamen kabul ediyorum (MySQL'in kullanımı oldukça kolaydır). Bu koşuyu alana kadar, Angus'un commçözümünü seviyorum , ancak pek çok insan grepbunu deniyor ve yanlış anlıyor grep.

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

İlk grepanahtarları alır. Üçüncüsü grep(içinde <(...)), büyük dosyada kullanılan tüm anahtarları alır ve ikinci grep'te <(...)argüman olarak bir dosya gibi geçirir -f. Bu, ikincisinin grepeşleşecek satırların listesi olarak kullanılmasına neden olur . Daha sonra bunu girişten (anahtar listesi) kanaldan (ilk olarak grep) eşleştirmek için kullanır -vve büyük dosyaya değil, anahtar dosyadan çıkarılan anahtarları yazdırır .

Elbette bunu izlemeniz ve silmeyi hatırlamanız gereken geçici dosyalarla yapabilirsiniz:

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

Bu, içinde allkeysgörünmeyen tüm satırları yazdırır usedkeys.


Ne yazık ki yavaş ve 40 saniye sonra bir bellek hatası alıyorum:grep: Memory exhausted
Peter.O

@ Peter.O Ama doğru. Her neyse, bu yüzden bir veritabanı öneriyorum ya da commbu sırayla.
Kevin

Evet işe yarıyor ama döngüden çok daha yavaş.
Teresa e Junior

1

Anahtar dosyası değişmiyor mu? Ardından eski girişleri tekrar tekrar aramaktan kaçınmalısınız.

İle tail -fbüyüyen bir dosyanın çıktısını alabilirsiniz.

tail -f growingfile | grep -f keyfile 

grep -f bir dosyadaki kalıpları, bir satır kalıp olarak okur.


Bu iyi olurdu, ancak anahtar dosya her zaman farklıdır.
Teresa e Junior

1

Cevabımı yayınlamayacaktım çünkü böyle bir verinin bir kabuk betiği ile işlenmemesi gerektiğini düşündüm ve bir veritabanı kullanmak için doğru cevap zaten verildi. Ama şu andan beri 7 yaklaşım daha var ...

Bellekteki ilk dosyayı okur, ardından ikinci dosyayı numaralar için arar ve değerlerin bellekte depolanıp depolanmadığını kontrol eder. grepTüm dosyayı yüklemek için yeterli belleğiniz varsa, birden fazla s'den daha hızlı olmalıdır .

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

Yeterli belleğim var, ama bunu daha da yavaş buldum. Yine de teşekkürler!
Teresa e Junior

1

@ Jan-steinman ile bu tür bir görev için bir veritabanı kullanmanız gerektiğine katılıyorum . Diğer cevapların gösterdiği gibi bir kabuk betiği ile bir çözümü hacklemenin birçok yolu vardır, ancak bu şekilde yapmak, kodu herhangi bir süre boyunca kullanacak ve koruyacaksanız çok fazla sefalete yol açacaktır. sadece bir günlük bir proje.

Bir Linux kutusunda olduğunuzu varsayarsak, muhtemelen Python v2.5'ten itibaren sqlite3 kütüphanesini içeren varsayılan olarak Python yüklüdür . Python sürümünüzü şunlarla kontrol edebilirsiniz:

% python -V
Python 2.7.2+

Tüm platformlar (web tarayıcınızın içinde dahil!) İçin var olan basit bir dosya tabanlı çözüm olduğundan ve bir sunucunun kurulmasını gerektirmediğinden sqlite3 kütüphanesini kullanmanızı öneririm . Temelde sıfır yapılandırma ve sıfır bakım.

Aşağıda örnek olarak verdiğiniz dosya biçimini ayrıştıracak ve daha sonra basit bir "tümünü seç" sorgusu yapacak ve db'de sakladığı her şeyi çıktılayacak basit bir python betiği bulunmaktadır.

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

Evet, bu, bazı SQL öğrenmeniz gerektiği anlamına gelir , ancak uzun vadede buna değecektir. Ayrıca, günlük dosyalarınızı ayrıştırmak yerine, doğrudan sqlite veritabanınıza veri yazabilirsiniz.


Python betiği için teşekkürler! Bence hiç kullanmamış olsam da, /usr/bin/sqlite3kabuk betikleri için aynı şekilde çalışır ( Packages.debian.org/squeeze/sqlite3 ).
Teresa e Junior

Evet, /usr/bin/sqlite3kabuk komut dosyalarıyla kullanabilirsiniz , ancak basit atma programları dışında kabuk komut dosyalarından kaçınmanızı ve bunun yerine daha iyi hata işlemeye sahip ve bakımı ve büyümesi daha kolay olan python gibi bir dil kullanmanızı öneririz.
aculich
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.