Як порахувати поява тексту у файлі?


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 кращих порушників сторінки входу до системного адміністратора з рекомендаціями щодо заборони відповідних підмереж. наприклад, один IP потрапив на сторінку входу понад 9000 разів. що IP та його підмережа класу D тепер перебувають у чорному списку. Я впевнений, що ми могли б це автоматизувати, хоча це вже інше питання.
j0h

Відповіді:


13

Ви можете використовувати grepі uniqдля списку адрес, перебирайте їх 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, який підраховує кількість рядків щонайменше з одним збігом.

Приклад виконання

$ 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-адреси, що буде дуже повільним, якщо файл великий. Інші рішення, які використовують uniq -cабо awkпотребують лише одного разу прочитати файл,
Девід

1
@David це правда, але це був би і перший мій погляд на це, знаючи, що греп має значення. Якщо продуктивність не є вимірною проблемою ... не передчасно оптимізувати?
D. Ben Knoble

3
Я б не назвав це передчасною оптимізацією, враховуючи, що більш ефективне рішення також простіше, але для кожного своє.
Девід

До речі, чому це написано як <log grep ...і ні grep ... log?
Сантьяго

@Santiago Тому що це краще в багатьох відношеннях, як пояснює тут Стефан Шазелас на U&L .
десерт

39

Ви можете використовувати cutта uniqінструменти:

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/'щоб отримати вихід точно так, як хотів ОП.
десерт

2
Це має бути прийнятою відповіддю, оскільки один за десертом потрібно читати файл неодноразово, тому це набагато повільніше. І ви можете легко використовувати, sort file | cut .... якщо ви не впевнені, чи файл уже відсортований.
Гунтрам Блом підтримує Моніку

14

Якщо вам конкретно не потрібен даний вихідний формат, я рекомендую вже опублікований cut+ uniqзаснований відповідь

Якщо вам дійсно потрібен даний вихідний формат, це буде однопрохідний спосіб зробити це в 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

було б легко змінити відповідь на основі cut + uniq на sed, щоб вона відображалася в потрібному форматі.
Пітер - Відновіть Моніку

@ PeterA.Schneider - так, я вважаю, на це вже вказувалося в коментарях до цієї відповіді
steeldriver

Ага, так, я бачу.
Пітер - Відновіть Моніку

8

Ось одне можливе рішення:

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. Ben Knoble

1
Це означає, що вам потрібно обробити весь файл кілька разів. Один раз, щоб отримати список IP-адрес, а потім ще раз для кожного 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

Це та сама ідея, як і підхід Стельдрівера , але в Perl. В -aпричини Perl автоматично розділити кожен рядок введення в масив @F, чий перший елемент (ІС) $F[0]. Отже, $k{$F[0]}++буде створено хеш %k, ключі якого є IP-адресами та значення яких - кількість разів, яку бачив кожен IP. Це }{прикольний мовний виступ для "зробити решту в самому кінці, обробивши всі дані". Отже, наприкінці сценарію буде повторюватися клавіші хеша та надрукувати поточний ключ ( $_) разом із його значенням ( $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

Можливо, це не те, чого хочуть ОП; однак, якщо ми знаємо, що довжина IP-адреси буде обмежена 15 символами, більш швидкий спосіб відображення рахунків за допомогою унікальних IP-адрес з величезного файлу журналу може бути досягнутий лише за допомогою uniqкоманди:

$ 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 Nпорівнює не більше Nсимволів у рядках

-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
Напевно, досить добре на практиці, але варто відзначити кутові випадки. Тільки 6 ймовірно постійних символів після IP `- - [`. Але теоретично адреса може бути до 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підказує йому рахувати події.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.