巨大なファイルから膨大な数のパターンをGrep


18

私は1日に約200,000行成長しているファイルを持っていますが、すべて3行のブロックで構成されています。

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

これで、別のファイルから約10,000個のキーパターンを抽出できます1358726575123。次にfor、これらのパターンでループを実行し、最初のファイルと照合する必要があります。ファイルにそのようなパターンが含まれていない場合、さらに処理するためにパターンを3番目のファイルに保存します。

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

サンプルコードでは、巨大なファイルを10,000回実行し、このループを1日に1回実行します。

巨大なファイルが成長し続けるので、これをすべて高速にしてCPUを節約するにはどうすればよいですか?ファイルをそのキーで何らかの方法で並べ替えるのであれば(そうであれば、どのように?)、プレーンテキストの代わりにdbを使用すると役立つのでしょうか...


回答:


11

この回答はawkpotongによって投稿された回答に基づいています.. メインファイルの同じ600万行1万のキーについて、(私のシステムで)メソッドの
2倍の速さです...(FNRを使用するように更新されました。 NR) comm

けれどもはawkより速く、あなたの現在のシステムよりも、そして、あなたとあなたのコンピュータ(sで)いくつかの呼吸スペースを与えるあなたが説明してきたようにデータ処理がとして強烈であるとき、あなたは、専用のデータベースに切り替えることにより、全体的に最良の結果を得ることを承知しているであろう。例えば。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


これは速いですが、私はawkの多くを理解していません:ファイル名はどのように見えるべきですか?私が試したfile1 -> mainfilefile2 -> keysのgawkとのmawkと、それは間違ったキーを出力します。
テレサeジュニア

file1にはキー、名前、およびジョブがあります。
テレサeジュニア

'mainfile'は大きなファイルです(キー、名前、およびジョブを含む)。「私は(FILE2対FILE1)したファイル混合アップを取得保管するので.. '私はちょうど「mainfileそれを呼ばれてきたキーのあなたのsituatonのためにのみ1万、またはしかし多く、キー..を含んではいけない、リダイレクト anytingを。 ..単にfile1 EOF file2 を使用します。これらはファイルの名前です。「EOF」は、最初のファイル(メインデータファイル)の終わりと2番目のファイルの始まりを示すスクリプトによる1行のファイルです。キー)。 awk一連のファイルを読み込むことができます。この場合、そのシリーズには3つのファイルがあります。出力はstdout
Peter.O

このスクリプトは、存在している任意のキーを表示しますmainfileそれはまたのいずれかのキーが印刷されますkeysされているファイルはありませんmainfile...何が起こっているのか、おそらくだそれ...(私はさらにそれにビットを見ていきます...
Peter.O

ありがとう、@ Peter.O!ファイルは機密である$RANDOMため、アップロード用のサンプルファイルを作成しようとしています。
テレサeジュニア

16

もちろん、問題は、大きなファイルに対してgrepを10,000回実行することです。両方のファイルを一度だけ読む必要があります。スクリプト言語の外に滞在したい場合は、次の方法で実行できます。

  1. ファイル1からすべての数値を抽出し、並べ替えます
  2. ファイル2からすべての数値を抽出して並べ替えます
  3. commソートされたリストで実行して、2番目のリストにのみあるものを取得します

このようなもの:

$ 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

をご覧くださいman comm

大きなファイル(ログファイルなど)を毎日切り捨てることができれば、ソートされた数値のキャッシュを保持でき、毎回全体を解析する必要はありません。


1
きちんとした!メインファイルに200,000のランダムな行エントリ(つまり600,000行)と143,000のランダムなキー(これが私のテストデータの最終的な方法です)で2秒(特に高速ドライブではない)テストし、動作します(しかし、あなたはそれを知っていました: )... {12}.. OPは12を使用していますが、例のキーは13の長さです
...-Peter.O

2
ほんの少し注意してください<(grep...sort)。ファイル名の場所を使用することで、一時ファイルを処理せずに実行できます。
ケビン

ありがとうございますが、ファイルのgrep処理と並べ替えには、前回のループ(+2分)よりもかなり時間がかかります。
テレサeジュニア

@テレサeジュニア。メインファイルの大きさは?... 1日あたり200,000行で増加することを言及しましたが、それほど大きくありません...処理する必要のあるデータ量を減らすために、次のことに注意して現在の200,000行だけを読むことができます。処理された最後の行番号(昨日)およびtail -n +$linenum最新のデータのみを出力するために使用します。そうすれば、毎日約20万行のみを処理することになります。メインファイルの600万行と1万のキーでテストしました... 時間:実0m0.016s、ユーザー0m0.008s、sys 0m0.008s
Peter.O

私は実際にはかなり、あなたのメインのファイルをgrepする方法についての好奇心/戸惑ってる回と高速化のみをgrepするこの方法よりも、それを見つけたら(とはるかに小さいため一度FILE1)...あなたのソートが長い私以上かかる場合であってもテスト、私はちょうど何度も大きなファイルを読むことが単一のソート(時間的に)を上回らないという考えを回避することはできません
-Peter.O

8

はい、間違いなくデータベースを使用してください。彼らはまさにこのようなタスクのために作られています。


ありがとう!私はデータベースの経験があまりありません。どのデータベースをお勧めしますか?MySQLとsqlite3コマンドがインストールされています。
テレサeジュニア

1
これらは両方ともこれで問題ありません。sqliteは基本的にファイルとそれにアクセスするSQL APIであるため、よりシンプルです。MySQLを使用するには、MySQLサーバーを設定する必要があります。それもそれほど難しいことではありませんが、sqliteを使用することをお勧めします。
ミカフィッシャー

3

これはあなたのために働くかもしれません:

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

編集:

両方のファイルで重複と不明なキーを許可するように修正されたスクリプトは、2番目には存在しない最初のファイルからキーを生成します。

 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

これにより、メインファイルで2回以上発生する新しいキーが失われます(さらに、キーファイルで2回以上発生する)またはいくつかの同等の回避策(+1それはかなり近いマークになるため)
Peter.O

1
...私はgawkのとのmawkと試みたが、それは間違ったキーを出力
テレサ・電子ジュニア

@ Peter.OIは、メインファイルには一意のキーがあり、そのファイル2はメインファイルのサブセットであると想定していました。
ポトン

@potong 2番目の方法は非常に高速で動作します。ありがとうございました!
テレサeジュニア

@Teresa e Juniorまだ正しく動作していますか?.. 5000キーを出力するテストデータを使用すると、実行時に136703キーが生成されます。 ... @potongもちろん!FNR == NR(これまで使ったことはありません:)
Peter.O

2

大量のデータがあるため、実際にデータベースに切り替える必要があります。それまでの間、まともなパフォーマンスを得るために行う必要があることの1つはfile1、各キーを個別に検索しないことです。シングルgrepを実行して、除外されていないすべてのキーを一度に抽出します。grepまた、キーを含まない行が返されるので、それらをフィルタリングします。

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

-Fx文字通り行全体を検索することを意味し-f -ます。標準入力からパターンのリストを読み取ることを意味します。)


誤解しない限り、これは大きなファイルにないキーを保存する問題に対処するものではなく、その中にあるキーを保存します。
ケビン

@Kevin正確に、これは私がループを使用することを余儀なくされました。
テレサeジュニア

@TeresaeJunior:追加-v-Fxv)と、そのの世話をすることがあります。
追って通知があるまで一時停止します。

などの名前、仕事、など、キーファイル内のいずれにも一致していない大きなファイル内のすべての行を選ぶだろう@DennisWilliamson
ケビン

@ケビンありがとう、質問を読み間違えた。キー行以外のフィルターを追加しましたが、現在はを使用commするようになっています。
ジル 'SO-悪であるのをやめる

2

他の人が言ったことを補強することを許可します。「データベースにアクセスしてください!」

ほとんどのプラットフォームで自由に利用できるMySQLバイナリがあります。

なぜSQLiteではありませんか?メモリベースで、起動時にフラットファイルをロードし、完了したらフラットファイルを閉じます。これは、コンピューターがクラッシュしたり、SQLiteプロセスがなくなると、すべてのデータもなくなることを意味します。

あなたの問題はほんの数行のSQLのように見え、ミリ秒で実行されます!

MySQLをインストールした後(他の選択肢よりもお勧めします)、Anthony MolinaroによるO'ReillyのSQL Cookbookに 40ドルを払いますSELECT * FROM table


はい、数日中にデータのSQLへの移行を開始します。ありがとうございます。ただし、awkスクリプトは、すべて完了するまで大いに役立ちました。
テレサeジュニア

1

これがあなたが探している正確な出力であるかどうかはわかりませんが、おそらく最も簡単な方法は次のとおりです:

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

以下も使用できます。

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

これらはそれぞれ、大きなファイル(file1)から数値を収集するために使用される一時的なパターンファイルを作成します。


私はこれも大きなファイルにある数字ではなく、そうでない数字を見つけると信じています。
ケビン

正しい、「!」が表示されませんでした OPで。のgrep -vf代わりに使用するだけです grep -f
アルセージュ

2
@arcegeはありません。grep-vfは一致しないキーを表示しません。名前やジョブを含むすべてを表示します。
テレサeジュニア

1

データベースを取得することに完全に同意します(MySQLは非常に使いやすいです)。あなたがそれを実行するまで、私はアンガスのcommソリューションが好きですが、非常に多くの人々が試しgrepて間違っているので、私は(または少なくとも1つの)正しい方法を示すと思いましたgrep

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

最初grepのキーを取得します。3番目grep(内<(...))は、大きなファイルで使用されるすべてのキーを取得し、2番目のgrepの<(...)引数としてファイルのように渡します-f。これにより、2番目はgrep一致する行のリストとしてそれを使用します。次に、これを使用して、パイプ(最初grep)からの入力(キーのリスト)に一致させ-v、大きなファイルではなくキーファイルから抽出されたキー()を出力します。

もちろん、一時ファイルを使用してこれを行うことができます。一時ファイルを追跡し、忘れずに削除する必要があります。

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

これにより、にallkeys表示されないすべての行が印刷されusedkeysます。


残念ながら、それは遅い、と私は40秒後にメモリエラーを取得:grep: Memory exhausted
Peter.O

@ Peter.Oしかし、それは正しいです。とにかく、だからこそ、データベースまたはcommをその順序で提案するのです。
ケビン

はい、動作しますが、ループよりもはるかに遅いです。
テレサeジュニア

1

キーファイルは変更されませんか?その後、古いエントリを何度も検索しないでください。

ではtail -f、あなたは成長しているファイルの出力を得ることができます。

tail -f growingfile | grep -f keyfile 

grep -fは、1行をパターンとしてファイルからパターンを読み取ります。


それは良いことですが、キーファイルは常に異なります。
テレサeジュニア

1

このような量のデータをシェルスクリプトで処理するべきではないと考えたため、回答を投稿しませんでした。データベースを使用するための正しい答えは既に与えられていました。しかし、現在では他に7つのアプローチがあります...

メモリ内の最初のファイルを読み取り、2番目のファイルの数値を取得し、値がメモリに保存されているかどうかを確認します。grepつまり、ファイル全体をロードするのに十分なメモリがある場合、複数のs よりも高速になります。

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

十分なメモリがありますが、これはさらに遅いことがわかりました。でもありがとう!
テレサeジュニア

1

@ jan-steinmanには、この種のタスクにはデータベースを使用する必要があることに同意します。他の回答が示すように、シェルスクリプトを使用してソリューションをハックする方法はたくさんありますが、その方法を使用すると、コードを使用して、たった1日の使い捨てプロジェクトです。

Linuxボックスを使用していると仮定すると、Python v2.5の時点でsqlite3ライブラリを含むPythonがデフォルトでインストールされている可能性があります。Pythonのバージョンは次の方法で確認できます。

% python -V
Python 2.7.2+

sqlite3ライブラリの使用をお勧めします。これは、すべてのプラットフォーム(Webブラウザー内を含む)に存在するシンプルなファイルベースのソリューションであり、サーバーをインストールする必要がないためです。本質的にゼロ構成とゼロ保守。

以下は、例として指定したファイル形式を解析し、単純な「すべて選択」クエリを実行して、dbに保存されているすべてを出力する単純なpythonスクリプトです。

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

はい、これはSQL学ぶ必要があることを意味しますが、長期的にはそれだけの価値があるでしょう。また、ログファイルを解析する代わりに、sqliteデータベースに直接データを書き込むこともできます。


Pythonスクリプトをありがとう!/usr/bin/sqlite3シェルスクリプト(packages.debian.org/squeeze/sqlite3)でも同じように機能すると思いますが、使用したことはありません。
テレサeジュニア

はい、/usr/bin/sqlite3シェルスクリプトで使用できますが、単純なスローアウェイプログラムを除き、シェルスクリプトを避けることをお勧めします。代わりに、エラー処理が改善され、保守および拡張が容易なpythonのような言語を使用します。
-aculich
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.