2つのテキストファイルがあります。ファイル2には1,000,000を超えるログがあります。ファイル1には1行ずつIPアドレスがあります。ファイル2行を読み取り、ファイル1でこれらの行を検索したいのですが、つまり、次のようになります。
ファイル1:
34.123.21.32
45.231.43.21
21.34.67.98
ファイル2:
34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200]
34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200]
21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200]
...
ファイル2のファイル1から1行ずつIPを検索し、時間引数(例:0.326)を新しいファイルに出力したいと思います。
これどうやってするの?
両方に存在するIPを見つけようとしている場合は、join
コマンドを使用できますが、ファイルを結合する前に、sort
を使用してファイルを事前に並べ替える必要があります。
$ join -o 2.2 <(sort file1) <(sort file2)
$ join -o 2.2 <(sort file1) <(sort file2)
1.765
0.326
4.754
3.673
6.334
ファイル1a:
$ cat file1a
34.123.21.32
45.231.43.21
21.34.67.98
1.2.3.4
5.6.7.8
9.10.11.12
ファイル2a:
$ cat file2a
34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200]
45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200]
34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200]
21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200]
1.2.3.4 1.234 - [30/Oct/2013:06:00:06 +0200]
4.3.2.1 4.321 - [30/Oct/2013:06:00:06 +0200]
join
コマンドの実行:
$ join -o 2.2 <(sort file1) <(sort file2)
1.234
1.765
0.326
4.754
3.673
6.334
注:file2
の元の順序は、最初にソートしたため、このメソッドでは失われます。ただし、この方法では、結果としてfile2
を1回スキャンするだけで済みます。
grep
を使用して、file2
にある行を使用して、file1
内の一致を検索できますが、この方法は、最初に示した方法ほど効率的ではありません。 file2
をスキャンして、file1
の各行を探します。
$ grep -f file1 file2 | awk '{print $2}'
$ grep -f file1 file2 | awk '{print $2}'
0.326
6.334
3.673
4.754
1.765
1.234
次のフォームを使用して、grep
のパフォーマンスを高速化できます。
$ LC_ALL=C grep -f file1 file2 | awk '{print $2}'
grep
に、file1
の刺し傷が固定長(-F
)であることを伝えることもできます。これは、パフォーマンスの向上にも役立ちます。
$ LC_ALL=C grep -Ff file1 file2 | awk '{print $2}'
ただし、一般的にソフトウェアでは、このアプローチは基本的にループタイプのソリューション内のループであるため、このアプローチを実行する必要はありません。しかし、コンピューターとソフトウェアを使用して達成できるのが最善の場合もあります。
grep
に、-f
スイッチ( POSIX標準 にある)を使用してファイルからパターンを取得するように指示できます。
sort file1 | uniq \ # Avoid duplicate entries in file1
| grep -f /dev/stdin file2 \ # Search in file2 for patterns piped on stdin
| awk '{print $2}' \ # Print the second field (time) for matches
> new_file # Redirect output to a new file
1つのIPアドレスがfile2
に複数回表示される場合、そのすべての時間エントリが出力されることに注意してください。
これは私のシステムの500万行のファイルで2秒未満で仕事をしました。
質問にタイトルを付けたのでbashプログラミングセミbashの例を送信します。
ip filter-fileを読み取ってから、行ごとにチェックして、これらと照合することができます。しかし、このボリュームでは本当に遅いです。
バブル–、選択–、挿入–、マージソートなどを簡単に実装することもできますが、この種のボリュームの場合は、行ごとの比較よりもうまくいかない可能性があります。 (フィルターファイルのボリュームに大きく依存します)。
もう1つのオプションは、ファイルをsort
で並べ替え、入力を社内で処理することです。二分探索。これも、ここに投稿された他の提案よりもはるかに遅くなりますが、試してみましょう。
まず、bashバージョンについての質問です。バージョン4(?)までに、ファイルを配列に読み取るmapfile
があります。これは、従来のread -ra …
よりもはるかに高速です。 sort
と組み合わせると、(このタスクの場合)次のようなスクリプトを作成できます。
mapfile arr <<< "$(sort -bk1,1 "$file_in")"
次に、この配列内の一致を見つけるための検索アルゴリズムを持つことについての質問です。簡単な方法は、バイナリ検索を使用することです。それは効率的であり、例えば1.000.000要素の配列は、かなり迅速なルックアップを提供します。
declare -i match_index
function in_array_bs()
{
local needle="$1"
local -i max=$arr_len
local -i min=0
local -i mid
while ((min < max)); do
(( (mid = ((min + max) >> 1)) < max )) || break
if [[ "${arr[mid]// *}" < "$needle" ]]; then
((min = mid + 1))
else
max=$mid
fi
done
if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then
match_index=$min
return 0
fi
return 1
}
次に、あなたは言うでしょう:
for x in "${filter[@]}"; do
if in_array_bs "$x"; then
… # check match_index+0,+1,+2 etc. to cover duplicates.
サンプルスクリプト。 (デバッグされていません)が、単なるスターターとして。 sort
のみに依存したいボリュームが少ない場合は、テンプレートにすることができます。しかし、再びs.l.o.w.e.r。b.ya。l.o.t。:
#!/bin/bash
file_in="file_data"
file_srch="file_filter"
declare -a arr # The entire data file as array.
declare -i arr_len # The length of "arr".
declare -i index # Matching index, if any.
# Time print helper function for debug.
function prnt_ts() { date +"%H:%M:%S.%N"; }
# Binary search.
function in_array_bs()
{
local needle="$1"
local -i max=$arr_len
local -i min=0
local -i mid
while ((min < max)); do
(( (mid = ((min + max) >> 1)) < max )) || break
if [[ "${arr[mid]// *}" < "$needle" ]]; then
((min = mid + 1))
else
max=$mid
fi
done
if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then
index=$min
return 0
fi
return 1
}
# Search.
# "index" is set to matching index in "arr" by `in_array_bs()`.
re='^[^ ]+ +([^ ]+)'
function search()
{
if in_array_bs "$1"; then
while [[ "${arr[index]// *}" == "$1" ]]; do
[[ "${arr[index]}" =~ $re ]]
printf "%s\n" "${BASH_REMATCH[1]}"
((++index))
done
fi
}
sep="--------------------------------------------"
# Timestamp start
ts1=$(date +%s.%N)
# Print debug information
printf "%s\n%s MAP: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$file_in" "$sep" >&2
# Read sorted file to array.
mapfile arr <<< "$(sort -bk1,1 "$file_in")"
# Print debug information.
printf "%s\n%s MAP DONE\n%s\n" \
"$sep" "$(prnt_ts)" "$sep" >&2
# Define length of array.
arr_len=${#arr[@]}
# Print time start search
printf "%s\n%s SEARCH BY INPUT: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$file_srch" "$sep" >&2
# Read filter file.
re_neg_srch='^[ '$'\t'$'\n'']*$'
debug=0
while IFS=$'\n'$'\t'-" " read -r ip time trash; do
if ! [[ "$ip" =~ $re_neg_srch ]]; then
((debug)) && printf "%s\n%s SEARCH: %s\n%s\n" \
"$sep" "$(prnt_ts)" "$ip" "$sep" >&2
# Do the search
search "$ip"
fi
done < "$file_srch"
# Print time end search
printf "%s\n%s SEARCH DONE\n%s\n" \
"$sep" "$(prnt_ts)" "$sep" >&2
# Print total time
ts2=$(date +%s.%N)
echo $ts1 $ts2 | awk '{printf "TIME: %f\n", $2 - $1}' >&2