web-dev-qa-db-ja.com

ファイルを1行ずつ比較し、新しい1つのbashプログラミングを作成します

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)を新しいファイルに出力したいと思います。

これどうやってするの?

2
DessCnk

参加+並べ替え

両方に存在する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

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}'

ただし、一般的にソフトウェアでは、このアプローチは基本的にループタイプのソリューション内のループであるため、このアプローチを実行する必要はありません。しかし、コンピューターとソフトウェアを使用して達成できるのが最善の場合もあります。

参考文献

3
slm

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秒未満で仕事をしました。

2
Joseph R.

質問にタイトルを付けたのでbashプログラミングセミbashの例を送信します。

純粋なbash:

ip filter-fileを読み取ってから、行ごとにチェックして、これらと照合することができます。しかし、このボリュームでは本当に遅いです。

バブル–、選択–、挿入–、マージソートなどを簡単に実装することもできますが、この種のボリュームの場合は、行ごとの比較よりもうまくいかない可能性があります。 (フィルターファイルのボリュームに大きく依存します)。

並べ替え+ bash:

もう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
1
Runium