web-dev-qa-db-ja.com

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

1日あたり約200,000行のファイルがあり、すべて3行のブロックで構成されています。

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

これで、1358726575123などの約10,000のキーパターンを抽出する別のファイルがあります。次に、これらのパターンを使用して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を使用することが役立つかどうか疑問に思います...

18
Teresa e Junior

この回答は、potong。によって投稿されたawk回答に基づいています。
同じ600万行commメソッド(私のシステム)の2倍の速さmain-fileおよび1万キー...(現在はFNR、NRを使用するように更新されています)

awkは現在のシステムよりも高速であり、あなたとあなたのコンピュータにある程度の呼吸スペースを与えますが、データ処理があなたが説明したほど激しい場合は、専用データベースへの切り替え。例えば。 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

11
Peter.O

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

  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

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

16
angus

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

8
Mika Fischer

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

 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
3
potong

他の人が言った「データベースに連れて行ってくれ!」を強化することを許可します。

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

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

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

MySQL(他の選択肢よりも推奨)をインストールした後、O'Reilly's SQL Cookbook 単純なSELECT * FROM tableクエリから始まり、集計と複数の結合を通過する、多くの問題パターンがあるAnthony Molinaroによる。

2
Jan Steinman

その大量のデータがあるので、本当にデータベースに切り替える必要があります。それまでの間、適切なパフォーマンスに近い場所に到達するために必要なことの1つは、検索しないことですfile1キーごとに個別に。単一のgrepを実行して、除外されていないすべてのキーを一度に抽出します。そのgrepもキーを含まない行を返すため、それらをフィルターで除外します。

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

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

キーファイルは変更されませんか?次に、古いエントリを何度も検索しないようにする必要があります。

tail -f拡大するファイルの出力を取得できます。

tail -f growingfile | grep -f keyfile 

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

1
user unknown

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

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

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
1
forcefsck

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

Linuxボックスを使用しているとすると、おそらくPythonがデフォルトでインストールされており、これには sqlite3ライブラリ が含まれますPython v2.5。Pythonバージョンを確認するには:

% python -V
Python 2.7.2+

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

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

#!/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データベースにデータを直接書き込むこともできます。

1
aculich

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

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)から数値を収集するために使用される一時的なパターンファイルを作成します。

1
Arcege

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

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

最初のgrepはキーを取得します。 3番目のgrep<(...)内)は、大きなファイルで使用されるすべてのキーを受け取り、<(...)はそれをファイルのように-fの引数としてファイルに渡します。 2番目のgrep。これにより、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のすべての行を出力します。

1
Kevin