Підрахуйте загальну кількість подій за допомогою grep


215

grep -cє корисним для того, щоб знайти кількість разів рядка у файлі, але він підраховує кожне виникнення один раз у рядку. Як порахувати кілька випадків на рядок?

Я шукаю щось більш елегантне, ніж:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'

4
Я знаю grep, що вказано, але для тих, хто використовує ack, відповідь проста ack -ch <pattern>.
Кайл Странд

Відповіді:


302

grep's -oвидаватиме лише сірники, ігноруючи рядки; wcможе їх порахувати:

grep -o 'needle' file | wc -l

Це також відповідатиме «голки» або «багатоноші».
Лише окремі слова:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l

6
Зауважте, що для цього потрібна грепка GNU (Linux, Cygwin, FreeBSD, OSX).
Жиль

@wag Що магія робить \bі \Bробить тут?
Geek

6
@Geek \ b відповідає межі слова, \ B відповідає НЕ межі слова. Відповідь вище була б правильнішою, якби вона використовувала \ b з обох кінців.
Ліам

1
Для підрахунку кількості подій у рядку поєднуйте з опцією grep -n та uniq -c ... grep -no '\ <голка \>' файл |
uniq

@jameswarren uniqвидаляє лише сусідні однакові лінії, до sortчого потрібно, uniqякщо ви не впевнені, що дублікати завжди будуть негайно сусідніми.
трійка

16

Якщо у вас є GNU Grep (завжди на Linux і Cygwin, іноді в інших місцях), ви можете розраховувати вихідні рядки зgrep -o : grep -o needle | wc -l.

З Perl, ось декілька способів, які я вважаю більш елегантними, ніж ваш (навіть після того, як це виправлено ).

perl -lne 'END {print $c} map ++$c, /needle/g'
perl -lne 'END {print $c} $c += s/needle//g'
perl -lne 'END {print $c} ++$c while /needle/g'

Маючи лише інструменти POSIX, один підхід, якщо це можливо, полягає в тому, щоб розділити вхід на рядки за допомогою однієї відповідності перед тим, як передати його в грепп. Наприклад, якщо ви шукаєте цілі слова, то спочатку перетворіть кожен несловесний символ у новий рядок.

# equivalent to grep -ow 'needle' | wc -l
tr -c '[:alnum:]' '[\n*]' | grep -c '^needle$'

Інакше не існує стандартної команди, щоб виконати цей конкретний біт обробки тексту, тому вам потрібно звернутися до sed (якщо ви мазохіст) або awk.

awk '{while (match($0, /set/)) {++c; $0=substr($0, RSTART+RLENGTH)}}
     END {print c}'
sed -n -e 's/set/\n&\n/g' -e 's/^/\n/' -e 's/$/\n/' \
       -e 's/\n[^\n]*\n/\n/g' -e 's/^\n//' -e 's/\n$//' \
       -e '/./p' | wc -l

Ось більш просте рішення з використанням sedі grep, яке працює для рядків або навіть звичайних регулярних виразів, але виходить з ладу у кількох кутових випадках із закріпленими візерунками (наприклад, воно знаходить два входження ^needleабо \bneedleв needleneedle).

sed 's/needle/\n&\n/g' | grep -cx 'needle'

Зауважимо, що в підстановці сед, наведених вище, я \nмав на увазі новий рядок. Це є стандартним у частині шаблону, але в тексті, що замінює, для портативності, підміняйте звороту косу рису на новий рядок \n.


4

Якщо, як я, ви насправді хотіли "обох; кожен рівно один раз", (це насправді "або два рази"), то це просто:

grep -E "thing1|thing2" -c

і перевірити вихід 2.

Перевага такого підходу (якщо саме один раз є те, що ви хочете) полягає в тому, що він легко масштабується.


Я не впевнений, що ти насправді перевіряєш, що з’являється лише один раз? Все, що ви шукаєте там, - це те, що будь-яке з цих слів існує хоча б раз.
Стів Горе

3

Ще одне рішення, що використовує awk та needleяк роздільник поля:

awk -F'^needle | needle | needle$' '{c+=NF-1}END{print c}'

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

awk -F'^needle[ ,.?]|[ ,.?]needle[ ,.?]|[ ,.?]needle$' '{c+=NF-1}END{print c}'

Або використовуйте клас: [^[:alnum:]]щоб охопити всі символи, що не належать до альфа.


Зауважте, що для цього потрібен awk, який підтримує розділові поля для регулярних виразів (наприклад, GNU awk).
Жиль

1

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

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 

Ви маєте рацію - мій приклад рахує лише випадки в першому рядку.

1

Це моє чисте баш-рішення

#!/bin/bash

B=$(for i in $(cat /tmp/a | sort -u); do
echo "$(grep $i /tmp/a | wc -l) $i"
done)

echo "$B" | sort --reverse
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.