web-dev-qa-db-fra.com

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

grep -c est utile pour trouver le nombre de fois qu'une chaîne apparaît dans un fichier, mais elle 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"'
242
user4518

grep's -o affichera uniquement les correspondances, en ignorant les lignes; wc peut les compter:

grep -o 'needle' file | wc -l

Cela correspondra également aux "aiguilles" ou "multi-aiguilles".

Pour ne faire correspondre que des mots uniques, utilisez l'une des commandes suivantes:

grep -ow 'needle' file | wc -l
grep -o '\bneedle\b' file | wc -l
grep -o '\<needle\>' file | wc -l
352
wag

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

Avec Perl, voici quelques façons que je trouve plus élégantes que les vôtres (même après que ce soit fixe ).

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 seulement les outils POSIX, une approche, si possible, consiste à diviser l'entrée en lignes avec une seule correspondance avant de la passer à grep. Par exemple, si vous recherchez des mots entiers, commencez par transformer chaque caractère non Word 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 particulier du texte, 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 sed et grep, qui fonctionne pour les chaînes ou même les expressions régulières du livre mais échoue dans quelques cas d'angle avec des modèles ancrés (par exemple, il trouve deux occurrences de ^needle ou \bneedle dans needleneedle).

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

Notez que dans les substitutions sed ci-dessus, j'ai utilisé \n pour désigner une nouvelle ligne. C'est standard dans la partie modèle, mais dans le texte de remplacement, pour la portabilité, remplacez la barre oblique inversée par \n.

Si, comme moi, tu voulais vraiment "les deux; chacun exactement une fois", (c'est en fait "soit; 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.

5
OJFord

Une autre solution utilisant awk et needle comme séparateur de champ:

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

Si vous souhaitez faire correspondre needle suivi de la ponctuation, modifiez le séparateur de champ en conséquence, c'est-à-dire.

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

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

3
ripat

Ceci est ma pure solution 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
1
Felipe

Votre exemple affiche uniquement le nombre d'occurrences par ligne, et non le total dans le fichier. Si c'est ce que vous voulez, quelque chose comme ça pourrait fonctionner:

Perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
1
jsbillings