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

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

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を見つけたい場合は、そのコマンドを使用できますが、ファイルを結合する前にそのコマンドを使用してファイルを事前にソートする必要がありますjoinsort

$ 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一致するものを検索できますが、この方法は私が示した最初の方法ほど効率的ではありません。検索のすべての行を検索します。file2file1file2file1

$ 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:-fPOSIX規格):

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

関連情報