Как посчитать вхождения текста в файл?

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"
j0h
источник
1
Под «bash» вы подразумеваете обычную оболочку или командную строку в целом?
десерт
1
У вас есть какое-либо программное обеспечение базы данных, доступное для использования?
SpacePhoenix
1
Связанные
Жюльен Лопес
Журнал поступает с сервера 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-адрес, за которым следуют «число» и количество. Последний вычисляется как 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
@ Давид, это правда, но это был бы и мой первый опыт, зная, что grep имеет значение. Если производительность не является измеримой проблемой ... не оптимизировать преждевременно?
Д. Бен Нобл
3
Я бы не назвал это преждевременной оптимизацией, учитывая, что более эффективное решение тоже проще, но каждому свое.
Дэвид
Кстати, а почему так написано, а <log grep ...нет grep ... log?
Сантьяго
@Santiago Потому что во многих отношениях лучше, поскольку Stéphane Chazelas объясняет здесь , на 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/'чтобы получить результат в точности так, как хотел OP.
десерт
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]}
'

Ex.

$ 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
steeldriver
источник
было бы легко изменить ответ на основе 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
pa4080
источник
1
Предпочитаю printf...
Д. Бен Нобл
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

Это та же идея, что и в awk-подходе Steeldriver , но в Perl. В -aпричины Perl автоматически разделить каждую строку ввода в массив @F, чей первый элемент (ИС) $F[0]. Таким образом, $k{$F[0]}++будет создан хеш %k, ключи которого - это IP-адреса, а значения - количество раз, которое каждый IP-адрес был просмотрен. Это }{в стиле фанк 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
terdon
источник
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
wjandrea
источник
0
cut -f1 -d- my.log | sort | uniq -c

Объяснение: Возьмите первое поле разбиения my.log на тире -и отсортируйте его. uniqнужен отсортированный ввод. -cговорит ему подсчитывать вхождения.

кандидат наук
источник