Лучший способ имитировать "группу по" из Bash? - PullRequest
203 голосов
/ 19 декабря 2008

Предположим, у вас есть файл, который содержит IP-адреса, по одному адресу в каждой строке:

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

Вам нужен сценарий оболочки, который подсчитывает для каждого IP-адреса, сколько раз он появляется в файле. Для предыдущего ввода вам понадобится следующий вывод:

10.0.10.1 3
10.0.10.2 1
10.0.10.3 1

Один из способов сделать это:

cat ip_addresses |uniq |while read ip
do
    echo -n $ip" "
    grep -c $ip ip_addresses
done

Однако это действительно далеко от эффективности.

Как бы вы решили эту проблему более эффективно, используя bash?

(Следует добавить: я знаю, что это можно решить с помощью perl или awk, меня интересует лучшее решение для bash, а не для этих языков).

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

Предположим, что исходный файл имеет 5 ГБ, а машина, на которой работает алгоритм, имеет 4 ГБ. Так что сортировка не является эффективным решением, и при этом чтение файла происходит не раз.

Мне понравилось решение, похожее на хеш-таблицу - кто-нибудь может предложить улучшения для этого решения?

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ # 2:

Некоторые люди спрашивали, зачем мне делать это в bash, когда это проще, например. Perl. Причина в том, что на машине, которую я должен был сделать, этот Perl был недоступен для меня. Это была специально созданная машина Linux без большинства инструментов, к которым я привык. И я думаю, что это была интересная проблема.

Так что, пожалуйста, не вините вопрос, просто проигнорируйте его, если он вам не нравится. : -)

Ответы [ 14 ]

362 голосов
/ 19 декабря 2008
sort ip_addresses | uniq -c

Сначала будет напечатан счетчик, но в остальном он должен быть именно тем, что вы хотите.

44 голосов
/ 19 декабря 2008

Быстрый и грязный метод выглядит следующим образом:

cat ip_addresses | sort -n | uniq -c

Если вам нужно использовать значения в bash, вы можете назначить всю команду переменной bash, а затем просмотреть результаты.

PS

Если команда сортировки опущена, вы не получите правильные результаты, поскольку uniq просматривает только последовательные идентичные строки.

20 голосов
/ 20 декабря 2008

Каноническое решение упомянуто другим респондентом:

sort | uniq -c

Это короче и лаконичнее, чем то, что можно написать на Perl или awk.

Вы пишете, что не хотите использовать сортировку, потому что размер данных больше размера основной памяти машины. Не стоит недооценивать качество реализации команды сортировки Unix. Сортировка использовалась для обработки очень больших объемов данных (например, исходных данных биллинга AT & T) на машинах с 128 КБ (это 131 072 байта) памяти (PDP-11). Когда сортировка встречает больше данных, чем предварительно установленный предел (часто настраиваемый близко к размеру основной памяти машины), она сортирует данные, прочитанные в основной памяти, и записывает их во временный файл. Затем он повторяет действие со следующими порциями данных. Наконец, он выполняет сортировку слиянием этих промежуточных файлов. Это позволяет сортировке работать с данными, во много раз превышающими основную память машины.

16 голосов
/ 10 апреля 2010

для суммирования нескольких полей на основе группы существующих полей, используйте приведенный ниже пример: (замените $ 1, $ 2, $ 3, $ 4 согласно вашим требованиям)

cat file

US|A|1000|2000
US|B|1000|2000
US|C|1000|2000
UK|1|1000|2000
UK|1|1000|2000
UK|1|1000|2000

awk 'BEGIN { FS=OFS=SUBSEP="|"}{arr[$1,$2]+=$3+$4 }END {for (i in arr) print i,arr[i]}' file

US|A|3000
US|B|3000
US|C|3000
UK|1|9000
8 голосов
/ 26 июля 2014
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'

эта команда выдаст желаемый результат

4 голосов
/ 19 декабря 2008

Похоже, вам нужно либо использовать большой объем кода для имитации хэшей в bash, чтобы получить линейное поведение, либо придерживаться квадратичных суперлинейных версий.

Среди этих версий решение saua является лучшим (и самым простым):

sort -n ip_addresses.txt | uniq -c

Я нашел http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html. Но это ужасно чертовски ...

3 голосов
/ 14 февраля 2014

Решение (сгруппировано по типу mysql)

grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n

Результат

3249  googleplus
4211 linkedin
5212 xing
7928 facebook
3 голосов
/ 21 декабря 2008

Я чувствую, что ассоциативный массив awk также удобен в этом случае

$ awk '{count[$1]++}END{for(j in count) print j,count[j]}' ips.txt

Группа по почте здесь

3 голосов
/ 20 декабря 2008

Вы, вероятно, можете использовать саму файловую систему в качестве хеш-таблицы. Псевдокод выглядит следующим образом:

for every entry in the ip address file; do
  let addr denote the ip address;

  if file "addr" does not exist; then
    create file "addr";
    write a number "0" in the file;
  else 
    read the number from "addr";
    increase the number by 1 and write it back;
  fi
done

В конце концов, все, что вам нужно сделать, - это перебрать все файлы и напечатать в них имена и номера файлов. В качестве альтернативы, вместо сохранения счетчика, вы можете каждый раз добавлять к файлу пробел или символ новой строки, а в конце просто смотреть на размер файла в байтах.

1 голос
/ 18 февраля 2018

Чистый (без вилки!)

Есть способ, использующий функцию . Этот путь очень быстрый, так как нет вилки! ...

... В то время как группа IP-адресов остается малая !

countIp () { 
    local -a _ips=(); local _a
    while IFS=. read -a _a ;do
        ((_ips[_a<<24|${_a[1]}<<16|${_a[2]}<<8|${_a[3]}]++))
    done
    for _a in ${!_ips[@]} ;do
        printf "%.16s %4d\n" \
          $(($_a>>24)).$(($_a>>16&255)).$(($_a>>8&255)).$(($_a&255)) ${_ips[_a]}
    done
}

Примечание. IP-адреса преобразуются в 32-разрядное целое число без знака, используемое в качестве индекса для массива . Здесь используются простые массивы bash , а не ассоциативный массив (который дороже)!

time countIp < ip_addresses 
10.0.10.1    3
10.0.10.2    1
10.0.10.3    1
real    0m0.001s
user    0m0.004s
sys     0m0.000s

time sort ip_addresses | uniq -c
      3 10.0.10.1
      1 10.0.10.2
      1 10.0.10.3
real    0m0.010s
user    0m0.000s
sys     0m0.000s

На моем хосте это происходит намного быстрее, чем при использовании вилок, примерно до 1000 адресов, но занимает около 1 секунды, когда я попытаюсь сортировать и считать 10000 адресов. .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...