Contar o número total de ocorrências usando grep

215

grep -cé útil para descobrir quantas vezes uma string ocorre em um arquivo, mas conta apenas cada ocorrência uma vez por linha. Como contar várias ocorrências por linha?

Estou procurando algo mais elegante do que:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
030
fonte
4
Eu sei que grepé especificado, mas para quem usa ack, a resposta é simples ack -ch <pattern>.
Kyle Strand #

Respostas:

302

o grep's -oproduzirá apenas as correspondências, ignorando as linhas; wcpode contá-los:

grep -o 'needle' file | wc -l

Isso também corresponderá a 'agulhas' ou 'multicamada'.
Apenas palavras simples:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l
abanar
fonte
6
Note que isso requer GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles
@ wag Que mágica faz \be \Bfaz aqui?
Geek
6
@Geek \ b corresponde a um limite de palavras, \ B NÃO corresponde a um limite de palavras. A resposta acima seria mais correta se usasse \ b nas duas extremidades.
Liam
1
Para uma contagem de ocorrências por linha, combine com a opção grep -n e uniq -c ... grep -no '\ <needle \>' file | uniq -c
jameswarren
O @jameswarren uniqremove apenas as linhas idênticas adjacentes, sortantes de alimentar, uniqse ainda não tiver certeza de que as duplicatas sempre serão imediatamente adjacentes.
Tripleee 3/11
16

Se você tiver GNU grep (sempre em Linux e Cygwin, ocasionalmente, em outros lugares), você pode contar as linhas de saídagrep -o : grep -o needle | wc -l.

Com o Perl, aqui estão algumas maneiras que considero mais elegantes que as suas (mesmo depois de consertadas ).

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'

Com apenas ferramentas POSIX, uma abordagem, se possível, é dividir a entrada em linhas com uma única correspondência antes de passá-la para grep. Por exemplo, se você estiver procurando por palavras inteiras, primeiro transforme todos os caracteres que não sejam palavras em uma nova linha.

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

Caso contrário, não há um comando padrão para fazer esse processamento específico de texto, então você precisa recorrer ao sed (se você é masoquista) ou 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

Aqui está uma solução mais simples usando sedand grep, que funciona para strings ou mesmo expressões regulares, mas falha em alguns casos de canto com padrões ancorados (por exemplo, encontra duas ocorrências de ^needleou \bneedleem needleneedle).

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

Observe que nas substituições sed acima, eu costumava \nsignificar uma nova linha. Isso é padrão na parte do padrão, mas no texto de substituição, para portabilidade, substitua a barra invertida-nova linha por \n.

Gilles
fonte
4

Se, como eu, você realmente queria "ambos; cada um exatamente uma vez" ((na verdade, é "qualquer um; duas vezes"), então é simples:

grep -E "thing1|thing2" -c

e verifique a saída 2.

O benefício dessa abordagem (se exatamente uma vez é o que você deseja) é que ela pode ser dimensionada facilmente.

OJFord
fonte
Não sei se você está verificando se está aparecendo apenas uma vez. Tudo o que você procura é que qualquer uma dessas palavras exista pelo menos uma vez.
Steve Gore
3

Outra solução usando awk e needlecomo separador de campo:

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

Se você deseja corresponder needleseguido de pontuação, altere o separador de campos de acordo.

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

Ou use a classe: [^[:alnum:]]para abranger todos os caracteres não alfa.

ripat
fonte
Observe que isso requer um awk que suporte separadores de campo regexp (como o GNU awk).
Gilles
1

Seu exemplo imprime apenas o número de ocorrências por linha, e não o total no arquivo. Se é isso que você deseja, algo como isso pode funcionar:

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
jsbillings
fonte
Você está certo - meu exemplo conta apenas as ocorrências na primeira linha.
1

Esta é a minha solução pura para o 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
Felipe
fonte