Cuenta el número total de ocurrencias usando grep

215

grep -ces útil para encontrar cuántas veces ocurre una cadena en un archivo, pero solo cuenta cada ocurrencia una vez por línea. ¿Cómo contar múltiples ocurrencias por línea?

Estoy buscando algo más elegante que:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
030
fuente
44
Sé que grepestá especificado, pero para cualquiera que lo use ack, la respuesta es sencilla ack -ch <pattern>.
Kyle Strand

Respuestas:

302

grep -osolo dará salida a las coincidencias, ignorando las líneas; wcpuede contarlos:

grep -o 'needle' file | wc -l

Esto también coincidirá con 'agujas' o 'multineedle'.
Solo palabras sueltas:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l
meneo
fuente
66
Tenga en cuenta que esto requiere GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles
@wag ¿Qué magia hace \by \Bhace aquí?
Geek
66
@ Geek \ b coincide con un límite de palabra, \ B no coincide con un límite de palabra. La respuesta anterior sería más correcta si usara \ b en ambos extremos.
Liam
1
Para un recuento de ocurrencias por línea, combine con la opción grep -n y uniq -c ... grep -no '\ <needle \>' file | uniq -c
jameswarren
@jameswarren uniqsolo elimina las líneas idénticas adyacentes, es necesario hacerlo sortantes de alimentar uniqsi no está seguro de que los duplicados siempre serán inmediatamente adyacentes.
tripleee
16

Si tiene grep de GNU (siempre en Linux y Cygwin, de vez en cuando en otros lugares), puede contar las líneas de salida degrep -o : grep -o needle | wc -l.

Con Perl, aquí hay algunas formas en que me parece más elegante que la tuya (incluso después de que se solucione ).

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'

Con solo herramientas POSIX, un enfoque, si es posible, es dividir la entrada en líneas con una sola coincidencia antes de pasarla a grep. Por ejemplo, si está buscando palabras completas, primero convierta cada carácter que no sea una palabra en una nueva línea.

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

De lo contrario, no hay un comando estándar para realizar este procesamiento de texto en particular, por lo que debe activar sed (si es un masoquista) o 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

Aquí hay una solución más simple que usa sedy grep, que funciona para cadenas o incluso expresiones regulares según el libro, pero falla en algunos casos de esquina con patrones anclados (por ejemplo, encuentra dos ocurrencias de ^needleo \bneedleen needleneedle).

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

Tenga en cuenta que en las sustituciones sed anteriores, solía \nsignificar una nueva línea. Esto es estándar en la parte del patrón, pero en el texto de reemplazo, para portabilidad, sustituya la barra diagonal inversa-nueva línea \n.

Gilles
fuente
4

Si, como yo, realmente quería "ambos; cada uno exactamente una vez", (esto es en realidad "cualquiera; dos veces"), entonces es simple:

grep -E "thing1|thing2" -c

y verifique la salida 2.

El beneficio de este enfoque (si exactamente una vez es lo que desea) es que se escala fácilmente.

OJFord
fuente
¿No estoy seguro de que realmente estás comprobando que solo aparece una vez? Todo lo que estás buscando es que cualquiera de esas palabras exista al menos una vez.
Steve Gore
3

Otra solución usando awk y needlecomo separador de campo:

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

Si desea coincidir needleseguido de puntuación, cambie el separador de campo en consecuencia, es decir

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

O use la clase: [^[:alnum:]]para abarcar todos los caracteres no alfa.

ripat
fuente
Tenga en cuenta que esto requiere un awk que admita separadores de campo regexp (como GNU awk).
Gilles
1

Su ejemplo solo imprime el número de ocurrencias por línea, y no el total en el archivo. Si eso es lo que quieres, algo como esto podría funcionar:

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
jsbillings
fuente
Tienes razón: mi ejemplo solo cuenta las ocurrencias en la primera línea.
1

Esta es mi solución bash pura

#!/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
fuente