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を使用することが役立つかどうか疑問に思います...
この回答は、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
もちろん問題は、大きなファイルに対して10,000回grepを実行することです。両方のファイルを一度だけ読み取る必要があります。スクリプト言語の外側にとどまりたい場合は、次の方法で行うことができます。
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
。
大きなファイル(ログファイルなど)を毎日切り捨てることができれば、並べ替えられた数値のキャッシュを保持でき、毎回全体を解析する必要はありません。
はい、間違いなくデータベースを使用してください。彼らはこのようなタスクのために正確に作られています。
これはあなたのために働くかもしれません:
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
他の人が言った「データベースに連れて行ってくれ!」を強化することを許可します。
ほとんどのプラットフォームで MySQLバイナリを自由に利用できます。
なぜSQLiteではないのですか?これはメモリベースであり、起動時にフラットファイルをロードし、終了したらフラットファイルを閉じます。これは、コンピュータがクラッシュしたり、SQLiteプロセスが停止したりすると、すべてのデータが停止することを意味します。
あなたの問題は数行のSQLのように見え、ミリ秒単位で実行されます!
MySQL(他の選択肢よりも推奨)をインストールした後、O'Reilly's SQL Cookbook 単純なSELECT * FROM table
クエリから始まり、集計と複数の結合を通過する、多くの問題パターンがあるAnthony Molinaroによる。
その大量のデータがあるので、本当にデータベースに切り替える必要があります。それまでの間、適切なパフォーマンスに近い場所に到達するために必要なことの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行をパターンとして読み取ります。
そのような量のデータはシェルスクリプトで処理されるべきではないと考え、データベースを使用する正しい答えは既に与えられているため、私の回答は投稿されませんでした。しかし、今から他に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
私は @ 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データベースにデータを直接書き込むこともできます。
これがあなたが探している正確な出力であるかどうかはわかりませんが、おそらく最も簡単な方法は次のとおりです。
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
)から数値を収集するために使用される一時的なパターンファイルを作成します。
私はあなたがデータベースを入手することに完全に同意します(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
のすべての行を出力します。