Найкращий спосіб імітувати "групувати" від bash?


231

Припустимо, у вас є файл, який містить 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:

Деякі люди запитували, чому я можу це робити в баш, коли це легше, наприклад, в перл. Причина полягає в тому, що на машині мені довелося зробити, що цей perl був для мене недоступним. Це була спеціально побудована машина Linux без більшості інструментів, до яких я звик. І я думаю, це була цікава проблема.

Тому, будь ласка, не звинувачуйте це питання, просто ігноруйте його, якщо воно вам не подобається. :-)


Я думаю, що bash - це неправильний інструмент для роботи. Perl, ймовірно, буде кращим рішенням.
Франсуа Вольмаранс

Відповіді:


412
sort ip_addresses | uniq -c

Це спочатку надрукує підрахунок, але крім того, що воно повинно бути саме таким, яке ви хочете.


71
який ви можете потім передати "сортувати -nr", щоб сортувати в порядку зменшення, від найвищого до найнижчого. тобтоsort ip_addresses | uniq -c | sort -nr
Бред Паркс

15
І sort ip_addresses | uniq -c | sort -nr | awk '{ print $2, $1 }'щоб отримати ip адресу в першому стовпчику і порахувати у другому.
Рагху Додда

ще один твір для сортування:sort -nr -k1,1
Анджей Мартина

50

Швидкий і брудний метод полягає в наступному:

cat ip_addresses | sort -n | uniq -c

Якщо вам потрібно використовувати значення в bash, ви можете призначити всю команду bash змінній, а потім провести цикл через результати.

PS

Якщо команда сортування опущена, ви не отримаєте правильних результатів, оскільки uniq переглядає лише послідовні однакові рядки.


Це дуже схоже на ефективність, у вас все ще є квадратична поведінка
Вінко Врсалович,

Квадратичне значення O (n ^ 2) ?? Це безумовно залежатиме від алгоритму сортування, навряд чи використовувати такий сорт bogo як такий.
paxdiablo

Ну, в кращому випадку це буде O (n log (n)), що гірше, ніж два проходи (це те, що ви отримуєте при тривіальній реалізації хеш-версії). Я мав би сказати «надлінійний», а не квадратичний.
Вінко Врсалович

І це все ще в тій же межі, що те, що просила ОП, щоб покращити ефективність ...
Вінко Врсалович,

11
uuoc, марне використання кота

22

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

2
+1, тому що він показує, що робити, коли потрібно не тільки кількість
user829755

1
+1 тому sortі uniqє найлегшими для виконання підрахунку, але не допомагають , коли вам потрібно обчислити / значення полів сум. Синтаксис масиву awk дуже потужний і ключовий для групування тут. Дякую!
одоні

1
ще одна річ, дивитися, що AWK в printфункції , здається, 64 біт вниз по шкалі цілих чисел до 32 біт, так що для ІНТ значень , що перевищують 2 ^ 31 ви можете використовувати printfз %.0fформатом замість printтам
odony

1
Люди, які шукають "групу по" зі сполученням рядків замість додавання числа, замінять, arr[$1,$2]+=$3+$4наприклад, arr[$1,$2]=(arr[$1,$2] $3 "," $4). I needed this to provide a grouped-by-package list of files (two columns only) and used: arr [$ 1] = (arr [$ 1] $ 2) `з успіхом.
Стефан Гурішон

20

Канонічне рішення - це те, що згадується іншим респондентом:

sort | uniq -c

Він коротший і стисліший, ніж те, що можна написати в Perl або awk.

Ви пишете, що не хочете використовувати сортування, оскільки розмір даних більший за основний об'єм пам'яті машини. Не варто недооцінювати якість виконання команди Unix сортування. Сортування було використано для обробки дуже великих обсягів даних (думаю, оригінальні дані про виставлення рахунків AT&T) на машинах з 128 кб (це 131 072 байт) пам'яті (PDP-11). Коли сортування стикається з більшою кількістю даних, ніж попередньо встановлений ліміт (часто налаштовується близько до розміру основної пам'яті машини), він сортує дані, які він прочитав, в основну пам'ять і записує їх у тимчасовий файл. Потім він повторює дію з наступними фрагментами даних. Нарешті, він виконує сортування злиття для цих проміжних файлів. Це дозволяє сортувати роботу з даними, багато разів більшими, ніж основна пам'ять машини.


Ну, це все-таки гірше, ніж хеш-кількість, ні? Чи знаєте ви, який алгоритм сортування використовує сортування, якщо дані вміщуються в пам'яті? Чи змінюється вона в регістрі числових даних (опція -n)?
Вінко Врсалович

Це залежить від способу реалізації (1). Як GNU сортування (використовується у дистрибутивах Linux), так і BSD сортування мають велику довжину, щоб використовувати найбільш відповідний алгоритм.
Діомідіс Шпінеліс

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

ця команда дасть вам бажаний вихід


4

Здається, що вам доведеться або використовувати велику кількість коду, щоб імітувати хеші в bash, щоб отримати лінійну поведінку, або дотримуватися квадратичних суперлінійних версій.

Серед цих версій найкращим (і найпростішим) є рішення сауї :

sort -n ip_addresses.txt | uniq -c

Я знайшов http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-11/0118.html . Але це некрасиво, як чорт ...


Я згоден. Це найкраще рішення на даний момент, і подібні рішення можливі в перл і невідомості. Чи може хтось забезпечити більш чисту реалізацію в bash?
Zizzencs

Не те, що я знаю. Ви можете отримати кращі реалізації в мовах, що підтримують хеші, де ви робите для мого $ ip (@ips) {$ hash {$ ip} = $ hash {$ ip} + 1; }, а потім просто надрукуйте ключі та значення.
Вінко Врсалович

4

Рішення (згрупувати на зразок mysql)

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

Результат

3249  googleplus
4211 linkedin
5212 xing
7928 facebook

3

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

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

Я відчуваю, що в цьому випадку корисний асоціативний масив

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

Групу поштою тут


Yepp, чудове рішення awk, але awk просто не було доступно на машині, на якій я робив це.
Zizzencs

1

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

Ось мій приклад даних:

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

1

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

Є спосіб, використовуючи a функція . Цей шлях дуже швидкий, оскільки немає вилки! ...

... Хоча купа 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

На моєму хості це зробити набагато швидше, ніж використовувати вилки, до приблизно 1 000 адрес, але зайняти приблизно 1 всю секунду, коли я спробую сортувати не рахувати 10 000 адрес.


0

Я зробив би це так:

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

але uniq може працювати для вас.


Як я вже говорив у початковій публікації, Perl - це не варіант. Я знаю, що в Perl це легко, проблем із цим немає :-)
Zizzencs

0

Я розумію, що ви шукаєте щось у Bash, але у випадку, якщо хтось ще може щось шукати в Python, ви можете розглянути це:

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

Оскільки значення в наборі унікальні за замовчуванням, і Python досить добре в цьому матеріалі, ви можете щось виграти тут. Я не перевіряв код, тому він може бути помилковим, але це може потрапити до вас. І якщо ви хочете порахувати події, використання дикта замість набору легко реалізувати.

Редагувати: Я хитрий читач, тому я відповів неправильно. Ось фрагмент із картинкою, яка б враховувала випадки.

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

Словник мій вирок тепер містить список унікальних IP-адрес як ключів та кількість разів, коли вони виникали як їх значення.


це нічого не рахує. тобі потрібен дикт, який зберігає бал.

До. Погане читання питання, вибачте. Спочатку я трохи щось про використання dict, щоб зберігати кількість разів, коли виникала кожна IP-адреса, але я її видалив, бо, ну, я не дуже добре прочитав питання. * намагається прокинутися належним чином
wzzrd

2
Є те, що в itertools.groupby()поєднанні з sorted()робить саме те, що задає ОП.
jfs

Це прекрасне рішення в python, яке для цього було недоступне :-)
Zizzencs

-8

Сортування може бути пропущено, якщо порядок не суттєвий

uniq -c <source_file>

або

echo "$list" | uniq -c

якщо джерельний список є змінною


1
Для подальшого уточнення на сторінці uniq man: Примітка: 'uniq' не виявляє повторюваних рядків, якщо вони не є суміжними. Ви можете спочатку впорядкувати вхід або використати 'сортувати -u' без 'uniq'.
конвертер42
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.