ファイル内のテキストの出現をカウントする方法は?


19

IPアドレスでソートされたログファイルがあります。一意の各IPアドレスの出現回数を調べたいです。bashでこれを行うにはどうすればよいですか?次のように、IPの横にあるオカレンスの数をリストする可能性があります。

5.135.134.16 count: 5
13.57.220.172: count 30
18.206.226 count:2

等々。

ログのサンプルは次のとおりです。

5.135.134.16 - - [23/Mar/2019:08:42:54 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "POST /wp-login.php HTTP/1.1" 200 3836 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:55 -0400] "POST /wp-login.php HTTP/1.1" 200 3988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
5.135.134.16 - - [23/Mar/2019:08:42:56 -0400] "POST /xmlrpc.php HTTP/1.1" 200 413 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:05 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:06 -0400] "POST /wp-login.php HTTP/1.1" 200 3985 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:07 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:08 -0400] "POST /wp-login.php HTTP/1.1" 200 3833 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:09 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:11 -0400] "POST /wp-login.php HTTP/1.1" 200 3836 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:12 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:15 -0400] "POST /wp-login.php HTTP/1.1" 200 3837 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.220.172 - - [23/Mar/2019:11:01:17 -0400] "POST /xmlrpc.php HTTP/1.1" 200 413 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
13.57.233.99 - - [23/Mar/2019:04:17:45 -0400] "GET / HTTP/1.1" 200 25160 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "https://www.google.com/url?3a622303df89920683e4421b2cf28977" "Mozilla/5.0 (Windows NT 6.2; rv:33.0) Gecko/20100101 Firefox/33.0"
18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] "POST /wp-login.php HTTP/1.1" 200 3988 "https://www.google.com/url?3a622303df89920683e4421b2cf28977" "Mozilla/5.0 (Windows NT 6.2; rv:33.0) Gecko/20100101 Firefox/33.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"
18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] "GET /wp-login.php HTTP/1.1" 200 2988 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0"

1
「bash」とは、一般的なプレーンシェルまたはコマンドラインのことですか?
デザート

1
使用可能なデータベースソフトウェアはありますか?
SpacePhoenix


ログは実際にはデータベースではなく、appache2サーバーからのものです。bashは、一般的なユースケースで私が好むものです。pythonとperlのソリューションを見ることができます。他の人に役立つのであれば、それは素晴らしいことです。最初の並べ替えは完了しましたsort -Vが、これは必須ではないと思います。ログインページの不正使用者トップ10を、それぞれのサブネットを禁止するための推奨事項とともにシステム管理者に送信しました。たとえば、1つのIPが9000回以上ログインページにヒットします。そのIP、およびそのクラスDサブネットは現在ブラックリストに登録されています。これは別の質問ですが、これを自動化できると確信しています。
j0h

回答:


13

アドレスのリストにgrepuniqを使用して、それらをループしgrep、カウントを繰り返します。

for i in $(<log grep -o '^[^ ]*' | uniq); do
  printf '%s count %d\n' "$i" $(<log grep -c "$i")
done

grep -o '^[^ ]*'は、先頭(^)から各行の最初のスペースまでのすべての文字を出力し、uniq繰り返し行を削除するため、IPアドレスのリストが残ります。コマンド置換のおかげで、forループはこのリストをループし、現在処理されているIPに続いて「count」とcountを出力します。後者はによって計算されgrep -c、少なくとも1つの一致がある行の数をカウントします。

実行例

$ for i in $(<log grep -o '^[^ ]*'|uniq);do printf '%s count %d\n' "$i" $(<log grep -c "$i");done
5.135.134.16 count 5
13.57.220.172 count 9
13.57.233.99 count 1
18.206.226.75 count 2
18.213.10.181 count 3

13
このソリューションは、IPアドレスごとに1回、入力ファイルを繰り返し処理しますが、ファイルが大きい場合は非常に遅くなります。ファイルを使用するuniq -cawk、ファイルを1回読み取るだけでよい他のソリューション
David

1
@Davidこれは真実ですが、これはgrepがカウントすることを知っていたので、これも私が最初に行ったことでした。パフォーマンスが測定可能な問題でない限り...時期尚早に最適化しないでください。
D.ベンノーブル

3
より効率的なソリューションも簡単ですが、それぞれ独自のものであるため、私はそれを時期尚早な最適化とは呼びません。
デビッド

ちなみに、どうして「ではなく」と書かれ<log grep ...ているのgrep ... logですか?
サンティアゴ

@SantiagoStéphaneChazelas がU&Lについてここで説明しているように、それは多くの点で優れているからです。
デザート

39

次のツールcutuniqツールを使用できます。

cut -d ' ' -f1 test.txt  | uniq -c
      5 5.135.134.16
      9 13.57.220.172
      1 13.57.233.99
      2 18.206.226.75
      3 18.213.10.181

説明 :

  • cut -d ' ' -f1 :最初のフィールドを抽出(IPアドレス)
  • uniq -c :繰り返し行を報告し、出現回数を表示します

6
を使用してsed、たとえばsed -E 's/ *(\S*) *(\S*)/\2 count: \1/'、OPが望むように出力を取得できます。
デザート

2
これは受け入れられる答えである必要があります。デザートによるものは繰り返しファイルを読み取る必要があるため、はるかに遅いです。またsort file | cut .... 、ファイルが既にソートされているかどうかわからない場合に簡単に使用できます。
Guntram Blohmは

14

特定の出力形式を特に必要としない場合は、既に投稿された+ ベースの回答をお勧めしますcutuniq

与えられた出力形式が本当に必要な場合、Awkでそれを行うシングルパス方法は

awk '{c[$1]++} END{for(i in c) print i, "count: " c[i]}' log

入力が既にソートされている場合、これはやや理想的ではありません。なぜなら、すべてのIPをメモリに不必要に保存するからですuniq -c

awk '
  NR==1 {last=$1} 
  $1 != last {print last, "count: " c[last]; last = $1} 
  {c[$1]++} 
  END {print last, "count: " c[last]}
'

$ awk 'NR==1 {last=$1} $1 != last {print last, "count: " c[last]; last = $1} {c[$1]++} END{print last, "count: " c[last]}' log
5.135.134.16 count: 5
13.57.220.172 count: 9
13.57.233.99 count: 1
18.206.226.75 count: 2
18.213.10.181 count: 3

要求された形式で表示されるように、sedを使用してcut + uniqベースの回答を変更するのは簡単です。
ピーター-モニカを

@ PeterA.Schneiderはい、それはです-私はすでにその答えをコメントで指摘されたことを信じて
steeldriver

ああ、はい、わかりました。
ピーター-モニカに

8

考えられる解決策の1つを次に示します。

IN_FILE="file.log"
for IP in $(awk '{print $1}' "$IN_FILE" | sort -u)
do
    echo -en "${IP}\tcount: "
    grep -c "$IP" "$IN_FILE"
done
  • file.log実際のファイル名に置き換えます。
  • コマンド置換式$(awk '{print $1}' "$IN_FILE" | sort -u)は、最初の列の一意の値のリストを提供します。
  • 次にgrep -c、ファイル内のこれらの各値をカウントします。

$ IN_FILE="file.log"; for IP in $(awk '{print $1}' "$IN_FILE" | sort -u); do echo -en "${IP}\tcount: "; grep -c "$IP" "$IN_FILE"; done
13.57.220.172   count: 9
13.57.233.99    count: 1
18.206.226.75   count: 2
18.213.10.181   count: 3
5.135.134.16    count: 5

1
好むprintf...
D.ベンKnoble

1
つまり、ファイル全体を複数回処理する必要があります。IPのリストを取得するために1回、その後、見つかったIPごとにもう一度。
テルドン

5

いくつかのPerl:

$ perl -lae '$k{$F[0]}++; }{ print "$_ count: $k{$_}" for keys(%k)' log 
13.57.233.99 count: 1
18.206.226.75 count: 2
13.57.220.172 count: 9
5.135.134.16 count: 5
18.213.10.181 count: 3

これは、Steeldriverのawkアプローチと同じ考え方ですが、Perlにあります。これ-aにより、perlは各入力行を配列に自動的に分割します。配列の@F最初の要素(IP)は$F[0]です。その$k{$F[0]}++ため、%kキーがIPで、値が各IPが表示された回数であるhashを作成します。}{「すべての入力を処理した後、一番最後に残りの作業を実行」のファンキーperlspeakです。そのため、最後に、スクリプトはハッシュのキーを反復処理し、現在のキー($_)とその値($k{$_})を出力します。

そして、perlがあなたに不可解な落書きのように見えるスクリプトを書くことを強制するとは思わないので、これはあまり凝縮されていない形で同じことです:

perl -e '
  while (my $line=<STDIN>){
    @fields = split(/ /, $line);
    $ip = $fields[0];
    $counts{$ip}++;
  }
  foreach $ip (keys(%counts)){
    print "$ip count: $counts{$ip}\n"
  }' < log

4

たぶん、これはOPが望むものではありません。ただし、IPアドレスの長さが15文字に制限されることがわかっている場合は、uniqコマンドのみを使用して、巨大なログファイルから一意のIPを含むカウントをすばやく表示する方法を実現できます。

$ uniq -w 15 -c log

5 5.135.134.16 - - [23/Mar/2019:08:42:54 -0400] ...
9 13.57.220.172 - - [23/Mar/2019:11:01:05 -0400] ...
1 13.57.233.99 - - [23/Mar/2019:04:17:45 -0400] ...
2 18.206.226.75 - - [23/Mar/2019:21:58:07 -0400] ...
3 18.213.10.181 - - [23/Mar/2019:14:45:42 -0400] ...

オプション:

-w NN行の文字以下を比較します

-c 出現回数を行の前に付けます

あるいは、正確にフォーマットされた出力の場合awk(IPV6アドレスでも機能するはずです)、ymmvです。

$ awk 'NF { print $1 }' log | sort -h | uniq -c | awk '{printf "%s count: %d\n", $2,$1 }'

5.135.134.16 count: 5
13.57.220.172 count: 9
13.57.233.99 count: 1
18.206.226.75 count: 2
18.213.10.181 count: 3

uniq彼らは隣接していない場合、それはする必要があるかもしれないので、入力ファイル内の重複行を検出しますないsortファイル。


1
実際には十分な可能性がありますが、重要なケースに注目する価値があります。IP `--[`の後には、たぶん 6つの定数文字のみ。ただし、理論上、アドレスは最大値よりも最大8文字短くなる可能性があるため、日付の変更によりそのようなIPのカウントが分割される可能性があります。そして、あなたが示唆するように、これはIPv6では機能しません。
マーティンソーントン

私はそれが好きです、私はuniqが数えることができるとは知りませんでした!
j0h

1

FWIW、Python 3:

from collections import Counter

with open('sample.log') as file:
    counts = Counter(line.split()[0] for line in file)

for ip_address, count in counts.items():
    print('%-15s  count: %d' % (ip_address, count))

出力:

13.57.233.99     count: 1
18.213.10.181    count: 3
5.135.134.16     count: 5
18.206.226.75    count: 2
13.57.220.172    count: 9

0
cut -f1 -d- my.log | sort | uniq -c

説明:my.logの最初のフィールドをダッシュ-で分割し、ソートします。uniqソートされた入力が必要です。-c発生をカウントするように指示します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.