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

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

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.


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.


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.


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

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.