Лучший способ имитировать "группу по" от bash?

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

10.0.10.1
10.0.10.1
10.0.10.3
10.0.10.2
10.0.10.1

Вам нужна оболочка script, которая подсчитывает каждый 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. Причина в том, что на машине я должен был сделать это, я не был доступен для меня. Это была специально построенная Linux-машина без большинства инструментов, к которым я привык. И я думаю, что это была интересная проблема.

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

Ответ 1

sort ip_addresses | uniq -c

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

Ответ 2

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

cat ip_addresses | sort -n | uniq -c

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

PS

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

Ответ 3

Каноническое решение - это тот, который упоминается другим респондентом:

sort | uniq -c

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

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

Ответ 4

для суммирования нескольких полей на основе группы существующих полей используйте пример ниже: (замените $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

Ответ 5

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

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

sort -n ip_addresses.txt | uniq -c

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

Ответ 6

cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'

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

Ответ 7

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

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

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

Ответ 8

Решение (группа с помощью mysql)

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

Результат

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

Ответ 9

Я понимаю, что вы ищете что-то в Bash, но если кто-то может что-то искать в Python, вы можете подумать об этом:

mySet = set()
for line in open("ip_address_file.txt"):
     line = line.rstrip()
     mySet.add(line)

Как значения в наборе по умолчанию уникальны, а Python довольно хорош в этом, вы можете что-то выиграть. Я не тестировал код, поэтому он может быть прослушан, но это может привести вас туда. И если вы хотите подсчитать случаи, использование dict вместо набора легко реализовать.

Изменить: Я паршивый читатель, поэтому я ответил неправильно. Здесь приведен фрагмент с диктоном, в котором будут учитываться события.

mydict = {}
for line in open("ip_address_file.txt"):
    line = line.rstrip()
    if line in mydict:
        mydict[line] += 1
    else:
        mydict[line] = 1

Теперь словарь mydict содержит список уникальных IP-ключей и количество раз, которое они имели в качестве своих значений.

Ответ 10

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

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

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

Ответ 11

Я бы сделал это вот так:

perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}\n";}' ip_addresses

но uniq может работать для вас.

Ответ 12

Большинство других решений подсчитывают дубликаты. Если вам действительно нужно группировать пары ключевых значений, попробуйте следующее:

Вот мои данные примера:

find . | xargs md5sum
fe4ab8e15432161f452e345ff30c68b0 a.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

Это будет печатать пары значений ключа, сгруппированные по контрольной сумме md5.

cat table.txt | awk '{print $1}' | sort | uniq  | xargs -i grep {} table.txt
30c68b02161e15435ff52e34f4fe4ab8 b.txt
30c68b02161e15435ff52e34f4fe4ab8 c.txt
fe4ab8e15432161f452e345ff30c68b0 a.txt
fe4ab8e15432161f452e345ff30c68b0 d.txt
fe4ab8e15432161f452e345ff30c68b0 e.txt

Ответ 13

Сортировка может быть опущена, если порядок не значим

uniq -c <source_file>

или

echo "$list" | uniq -c

если исходный список является переменной