Подсчитать общее количество вхождений с помощью grep

215

grep -cполезен для определения количества встреч строк в файле, но он учитывает каждый случай только один раз в строке. Как посчитать несколько вхождений на строку?

Я ищу что-то более элегантное, чем:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
+030
источник
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 grep (Linux, Cygwin, FreeBSD, OSX).
Жиль
@wag Что магия делает \bи \Bделать здесь?
Компьютерщик
6
@Geek \ b соответствует границе слова, \ B соответствует НЕ границе слова. Ответ выше был бы более правильным, если бы он использовал \ b на обоих концах.
Лиам
1
Для подсчета вхождений в каждой строке используйте параметр grep -n и uniq -c ... grep -no '\ <needle \>' file | uniq -c
jameswarren
@jameswarren uniqудаляет только смежные идентичные строки, которые вам нужно sortперед подачей, uniqесли вы еще не уверены, что дубликаты всегда будут соседними.
tripleee
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 одним из подходов, если это возможно, является разбиение ввода на строки с одним соответствием перед передачей его в grep. Например, если вы ищете целые слова, то сначала превратите каждый несловарный символ в новую строку.

# 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'

Обратите внимание, что в приведенных выше заменах sed я имел \nв виду перевод строки. Это стандартно в части шаблона, но в тексте замены, для переносимости, используется замена на обратную косую черту для новой строки \n.

жилль
источник
4

Если, как и я, вы на самом деле хотели «оба; каждый ровно один раз» (это на самом деле «либо; дважды»), тогда все просто:

grep -E "thing1|thing2" -c

и проверьте вывод 2.

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

OJFord
источник
Я не уверен, что вы на самом деле проверяете, что он появляется только один раз? Все, что вы ищете, это то, что одно из этих слов существует хотя бы один раз.
Стив Гор
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}' 
jsbillings
источник
Вы правы - мой пример учитывает только случаи в первой строке.
1

Это мое чистое решение от Bash

#!/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
Фелипе
источник