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

<p>предположим, у вас есть файл, который содержит ip-адреса, по одному адресу в каждой строке:</p><pre><code>10.0.10.1 10.0.10.1 10.0.10.3 10.0.10.2 10.0.10.1 </code></pre><p>вам нужен сценарий оболочки, который подсчитывает для каждого ip-адреса, сколько раз он появляется в файле. для предыдущего ввода вам понадобится следующий вывод:</p><pre><code>10.0.10.1 3 10.0.10.2 1 10.0.10.3 1 </code></pre><p>один из способов сделать это:</p><pre><code>cat ip\_addresses |uniq |while read ip do echo -n $ip" " grep -c $ip ip\_addresses done </code></pre><p>однако это действительно далеко не эффективно.</p><p>как бы вы решили эту проблему более эффективно, используя bash?</p><p>(следует добавить: я знаю, что это можно решить с помощью perl или awk, меня интересует лучшее решение для bash, а не для этих языков.)</p><p>дополнительная информация:</p><p>предположим, что исходный файл имеет размер 5 гб, а машина, на которой работает алгоритм, имеет 4 гб. так что сортировка не является эффективным решением, и при этом чтение файла не выполняется более одного раза.</p><p>мне понравилось решение, похожее на хеш-таблицу - кто-нибудь может предложить улучшения для этого решения?</p><p>дополнительная информация № 2:</p><p>некоторые люди спрашивали, зачем мне это делать в bash, когда это проще, например? perl. причина в том, что на машине, которую я должен был сделать, perl был недоступен для меня. это была специально созданная машина linux без большинства инструментов, к которым я привык. и я думаю, что это была интересная проблема.</p><p>поэтому, пожалуйста, не вините вопрос, просто проигнорируйте его, если он вам не нравится. :-)</p>

<pre><code>sort ip\_addresses | uniq -c </code></pre><p>сначала будет напечатан счетчик, но в остальном он должен быть именно тем, что вы хотите.</p>

<p>быстрый и грязный метод заключается в следующем:</p><p><code>cat ip\_addresses | sort -n | uniq -c</code></p><p>если вам нужно использовать значения в bash, вы можете назначить всю команду переменной bash, а затем просмотреть результаты.</p><p>ps</p><p>если команда сортировки опущена, вы не получите правильные результаты, поскольку uniq просматривает только последовательные идентичные строки.</p>

<p>каноническое решение упомянуто другим респондентом:</p><pre><code>sort | uniq -c </code></pre><p>это короче и более сжато, чем то, что можно написать на perl или awk.</p><p>вы пишете, что не хотите использовать сортировку, потому что размер данных больше, чем размер основной памяти машины. не стоит недооценивать качество реализации команды сортировки unix. сортировка использовалась для обработки очень больших объемов данных (например, исходных данных для выставления счетов at & t) на машинах с 128 кб (это 131 072 байта) памяти (pdp-11). когда сортировка встречает больше данных, чем предварительно установленный предел (часто настраиваемый близко к размеру основной памяти машины), она сортирует данные, прочитанные в основной памяти, и записывает их во временный файл. затем он повторяет действие со следующими порциями данных. наконец, он выполняет сортировку слиянием этих промежуточных файлов. это позволяет сортировке работать с данными, во много раз превышающими основную память машины.</p>

<p>для суммирования нескольких полей на основе группы существующих полей используйте приведенный ниже пример: (замените $ 1, $ 2, $ 3, $ 4 в соответствии с вашими требованиями)</p><pre><code>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 </code></pre>

<pre><code>cat ip\_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}' </code></pre><p>эта команда даст вам желаемый результат</p>

<p>похоже, вам нужно либо использовать большой объем кода для имитации хэшей в bash, чтобы получить линейное поведение, либо придерживаться <strike>квадратный</strike> суперлинейные версии.</p><p>среди этих версий решение <a href="https://stackoverflow.com/questions/380817/best-way-to-simulate-group-by-from-bash#380832">saua</a> является лучшим (и самым простым):</p><pre><code>sort -n ip\_addresses.txt | uniq -c </code></pre><p>я нашел <a href="http://unix.derkeiler.com/newsgroups/comp.unix.shell/2005-11/0118.html">http://unix.derkeiler.com/newsgroups/comp.unix.shell/2005-11/0118.html</a> . но это ужасно чертовски ...</p>

<p>я чувствую, awk ассоциативный массив также удобен в этом случае</p><pre><code>$ awk '{count[$1]++}end{for(j in count) print j,count[j]}' ips.txt </code></pre><p>группа по почте <a href="http://unstableme.blogspot.com/2008/09/group-by-clause-functionality-in-awk.html">здесь</a> </p>

<p>вы, вероятно, можете использовать саму файловую систему в качестве хеш-таблицы. псевдокод выглядит следующим образом:</p><pre><code>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 </code></pre><p>в конце концов, все, что вам нужно сделать, это пройти через все файлы и распечатать имена файлов и номера в них. в качестве альтернативы, вместо сохранения счетчика, вы можете каждый раз добавлять к файлу пробел или символ новой строки, а в конце просто смотреть на размер файла в байтах.</p>

<p>решение (сгруппировано как mysql)</p><pre><code>grep -ioh "facebook\|xing\|linkedin\|googleplus" access-log.txt | sort | uniq -c | sort -n </code></pre><p>результат</p><pre><code>3249 googleplus 4211 linkedin 5212 xing 7928 facebook </code></pre>

<h3>чистый <a href="/questions/tagged/bash">баш</a> (без вилки!)</h3><p>есть способ, используя функцию <em> <a href="/questions/tagged/bash">bash</a> </em>. этот путь очень быстрый, поскольку нет вилки! ...</p><p>... хотя куча <em> ip-адресов </em> остается <em> маленькой </em>!</p><pre><code>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-разрядное целое число без знака, используемое в качестве индекса для массива  . при этом используются простые <strong>  массивы bash  </strong>, а не <strong>  ассоциативный массив  </strong> (который дороже)!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 целую секунду, когда я попытаюсь  sort'n count  10 000 адресов.

я бы сделал это так:perl -e 'while (<>) {chop; $h{$_}++;} for $k (keys %h) {print "$k $h{$k}n";}' ip_addresses но uniq может работать для вас.

большинство других решений считают дубликаты. если вам действительно нужно сгруппировать пары ключ-значение, попробуйте это:вот мой пример данных: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 </code></pre>

<p>я понимаю, что вы ищете что-то в bash, но в случае, если кто-то еще ищет что-то в python, вы можете рассмотреть это:</p><pre><code>myset = set() for line in open("ip\_address\_file.txt"): line = line.rstrip() myset.add(line) </code></pre><p>так как значения в наборе уникальны по умолчанию и python довольно хорош в этом, вы можете выиграть что-то здесь. я не тестировал код, поэтому он может быть ошибочным, но это может привести вас к этому. и если вы хотите считать события, легко использовать dict вместо набора.</p><p>правка: я паршивый читатель, поэтому я ответил неправильно. вот фрагмент кода с указанием количества событий.</p><pre><code>mydict = {} for line in open("ip\_address\_file.txt"): line = line.rstrip() if line in mydict: mydict[line] += 1 else: mydict[line] = 1 </code></pre><p>словарь mydict теперь содержит список уникальных ip-адресов в качестве ключей и количество раз, которое они встречались в качестве их значений.</p>