2つのテキストファイルがあります。ファイル2には1,000,000を超えるログがあります。ファイル 1 には IP アドレスが 1 行ずつ含まれています。ファイル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]
...
ファイル1のIPをファイル2から1行ずつ検索し、時間パラメータ(例:0.326)を新しいファイルに印刷したいと思います。
どうすればいいですか?
答え1
結合+ソート
両方に存在する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
今回のスキャンのみが必要です。
grep
行を使用して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 パフォーマンスの向上
grep
次の形式を使用すると、パフォーマンスを高速化できます。
$ LC_ALL=C grep -f file1 file2 | awk '{print $2}'
grep
また、脊椎がfile1
固定長()であることがわかります。-F
これはより良いパフォーマンスを得るのに役立ちます。
$ LC_ALL=C grep -Ff file1 file2 | awk '{print $2}'
一般に、ソフトウェアで言うと、このアプローチは基本的にループ型ソリューション内のループなので避けたいと思います。しかし、時にはコンピュータ+ソフトウェアを使用して最良の結果を得ることができます。
引用する
答え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
IPアドレスがに複数回表示されると、対応する時間file2
エントリがすべて印刷されます。
これで、私のシステムで2秒以内に500万行のファイル操作が完了しました。
答え3
質問に名前を付けたときバッシュプログラミングセミバッシュの例を提出します。
純粋なバッシュ:
あなたは読むことができますIPフィルタ-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
ができます。しかし、はるかに遅い:
#!/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