Compter le nombre total d'occurrences à l'aide de grep

215

grep -cest utile pour déterminer le nombre de fois qu'une chaîne se produit dans un fichier, mais ne compte chaque occurrence qu'une fois par ligne. Comment compter plusieurs occurrences par ligne?

Je cherche quelque chose de plus élégant que:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
030
la source
4
Je sais grepest spécifié, mais pour quiconque utilise ack, la réponse est simple ack -ch <pattern>.
Kyle Strand

Réponses:

302

grep ne -osortira que les correspondances en ignorant les lignes; wcpeut les compter:

grep -o 'needle' file | wc -l

Cela correspondra également à «aiguilles» ou «multineedle».
Seulement des mots simples:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l
remuer
la source
6
Notez que cela nécessite GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles
@wag Qu'est - ce que la magie ne \bet \Bfaire ici?
Geek
6
@ Geek \ b correspond à une limite de mot, \ B ne correspond PAS à une limite de mot. La réponse ci-dessus serait plus correcte si elle utilisait \ b aux deux extrémités.
Liam
1
Pour un nombre d'occurrences par ligne, combinez les options grep -n et uniq -c ... grep -no '\ <aiguille \>' fichier | uniq -c
jameswarren
@jameswarren uniqne supprime que les lignes identiques adjacentes; vous devez le faire sortavant de vous alimenter uniqsi vous n'êtes pas déjà sûr que les doublons seront toujours immédiatement adjacents.
tripleee
16

Si vous avez GNU grep (toujours sur Linux et Cygwin, parfois ailleurs), vous pouvez compter les lignes de sortie degrep -o : grep -o needle | wc -l.

Avec Perl, voici quelques manières que je trouve plus élégantes que la vôtre (même après que ce soit réglé ).

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'

Avec les seuls outils POSIX, une approche, si possible, consiste à scinder l’entrée en lignes avec une seule correspondance avant de la transmettre à grep. Par exemple, si vous recherchez des mots entiers, commencez par transformer chaque caractère non-mot en une nouvelle ligne.

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

Sinon, il n'y a pas de commande standard pour effectuer ce traitement de texte particulier, vous devez donc vous tourner vers sed (si vous êtes masochiste) 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

Voici une solution plus simple utilisant sedand grep, qui fonctionne pour les chaînes ou même les expressions rationnelles mais qui échoue dans certains cas avec des motifs ancrés (par exemple, elle trouve deux occurrences de ^needleou \bneedledans needleneedle).

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

Notez que dans les substitutions sed ci-dessus, je voulais \ndire une nouvelle ligne. Ceci est standard dans la partie motif, mais dans le texte de remplacement, remplacez la barre oblique inverse par une nouvelle barre oblique inversée \n.

Gilles
la source
4

Si, comme moi, vous vouliez réellement "les deux; chacun exactement une fois", (c'est en fait "deux fois"), alors c'est simple:

grep -E "thing1|thing2" -c

et vérifiez la sortie 2.

L'avantage de cette approche (si exactement une fois est ce que vous voulez) est qu'elle évolue facilement.

OJFord
la source
Je ne suis pas sûr que vous vérifiiez qu'il n'apparaît qu'une seule fois? Tout ce que vous recherchez, c’est que l’un ou l’autre de ces mots existe au moins une fois.
Steve Gore
3

Une autre solution utilisant awk et needlecomme séparateur de champs:

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

Si vous souhaitez faire correspondre le needletexte suivi de la ponctuation, modifiez le séparateur de champs en conséquence, c.-à-d.

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

Ou utilisez la classe: [^[:alnum:]]pour englober tous les caractères non alpha.

ripat
la source
Notez que cela nécessite un awk qui supporte les séparateurs de champs regexp (tels que GNU awk).
Gilles
1

Votre exemple n'indique que le nombre d'occurrences par ligne et non le total du fichier. Si c'est ce que vous voulez, quelque chose comme ceci pourrait fonctionner:

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
jsbillings
la source
Vous avez raison - mon exemple ne compte que les occurrences de la première ligne.
1

Ceci est ma solution pure 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
la source