Как посчитать вхождения текста в файл?
У меня есть файл журнала, отсортированный по 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"
8 ответов
Ты можешь использовать 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
Ты можешь использовать 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
: сообщать о повторных строках и отображать количество вхождений
Если вам не требуется данный формат вывода, я бы порекомендовал 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
Вот одно из возможных решений:
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
Некоторые 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
, чей первый элемент (IP) $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
Может быть, это не то, чего хочет ОП; однако, если мы знаем, что длина 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
файл.
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
cut -f1 -d- my.log | sort | uniq -c
Пояснение: возьмите первое поле разбиения my.log на тире -
и сортируй это. uniq
нужен отсортированный ввод. -c
говорит ему подсчитывать вхождения.