it-swarm.com.ru

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

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

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

203
Zizzencs
sort ip_addresses | uniq -c

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

363
Joachim Sauer

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

cat ip_addresses | sort -n | uniq -c

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

PS

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

44
Francois Wolmarans

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

sort | uniq -c

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

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

20
Diomidis Spinellis

для суммирования нескольких полей на основе группы существующих полей используйте приведенный ниже пример: (замените $ 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
16
Anonymous
cat ip_addresses | sort | uniq -c | sort -nr | awk '{print $2 " " $1}'

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

9
zjor

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

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

sort -n ip_addresses.txt | uniq -c

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

4
Vinko Vrsalovic

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

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

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

3
Jadu Saikia

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

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

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

3
PolyThinker

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

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

Результат

3249  googleplus
4211 linkedin
5212 xing
7928 facebook
3
kairouan2020

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

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

... Хотя куча 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 целую секунду, когда я попытаюсь sort'n count 10 000 адресов.

1
F. Hauri

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

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

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

0
nicerobot

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

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

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
0
Aron Curzon

Я понимаю, что вы ищете что-то в 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-адресов в качестве ключей и количество раз, которое они встречались в качестве их значений.

0
wzzrd