Conta il numero totale di occorrenze utilizzando grep


215

grep -cè utile per determinare quante volte si verifica una stringa in un file, ma conta ogni ricorrenza una sola volta per riga. Come contare più ricorrenze per riga?

Sto cercando qualcosa di più elegante di:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'

4
So che grepè specificato, ma per chiunque utilizzi ack, la risposta è semplicemente ack -ch <pattern>.
Kyle Strand,

Risposte:


302

grep's -oprodurrà solo le partite, ignorando le linee; wcpuò contarli:

grep -o 'needle' file | wc -l

Ciò corrisponderà anche a "aghi" o "a più punte".
Solo parole singole:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l

6
Nota che ciò richiede GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles,

@wag Che magia fa \be \Bcosa fa qui?
Geek,

6
@Geek \ b corrisponde a un limite di parole, \ B NON corrisponde a un limite di parole. La risposta sopra sarebbe più corretta se usasse \ b ad entrambe le estremità.
Liam,

1
Per un conteggio delle occorrenze per riga, combinare con l'opzione grep -n e il file uniq -c ... grep -no '\ <ago \>' | uniq -c
jameswarren,

@jameswarren uniqrimuove solo le linee identiche adiacenti, è necessario sortprima di alimentare uniqse non si è già sicuri che i duplicati saranno sempre immediatamente adiacenti.
Tripleee,

16

Se si dispone di GNU grep (sempre su Linux e Cygwin, di tanto in tanto altrove), è possibile contare le linee di uscita dagrep -o : grep -o needle | wc -l.

Con Perl, ecco alcuni modi in cui trovo più elegante del tuo (anche dopo che è stato risolto ).

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 gli strumenti POSIX, un approccio, se possibile, è quello di dividere l'input in righe con una singola corrispondenza prima di passarlo a grep. Ad esempio, se stai cercando parole intere, prima trasforma ogni carattere non composto da parole in una nuova riga.

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

Altrimenti, non esiste un comando standard per eseguire questo particolare bit di elaborazione del testo, quindi è necessario passare a sed (se sei un masochista) 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

Ecco una soluzione più semplice che utilizza sede grep, che funziona con stringhe o anche espressioni regolari del libro, ma non riesce in alcuni casi angolari con motivi ancorati (ad esempio, trova due occorrenze di ^needleo \bneedlein needleneedle).

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

Si noti che nelle sostituzioni sed di cui sopra, volevo \ndire una nuova riga. Questo è standard nella parte del modello, ma nel testo sostitutivo, per la portabilità, sostituire backslash-newline per \n.


4

Se, come me, in realtà volevi "entrambi; ognuno esattamente una volta", (questo è in realtà "uno; due volte"), allora è semplice:

grep -E "thing1|thing2" -c

e controlla l'output 2.

Il vantaggio di questo approccio (se esattamente una volta è quello che vuoi) è che si ridimensiona facilmente.


Non sono sicuro che stai effettivamente controllando che appaia solo una volta? Tutto quello che stai cercando è che una di quelle parole esista almeno una volta.
Steve Gore,

3

Un'altra soluzione che utilizza awk e needlecome separatore di campo:

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

Se si desidera abbinare needleseguito dalla punteggiatura, modificare il separatore di campo di conseguenza, ad es

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

Oppure usa la classe: [^[:alnum:]]per includere tutti i caratteri non alfa.


Si noti che ciò richiede un awk che supporti i separatori di campi regexp (come GNU awk).
Gilles,

1

Il tuo esempio stampa solo il numero di occorrenze per riga e non il totale nel file. Se è quello che vuoi, qualcosa del genere potrebbe funzionare:

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

Hai ragione - il mio esempio conta solo le occorrenze nella prima riga.

1

Questa è la mia pura soluzione 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.