Zählen Sie die Gesamtanzahl der Vorkommen mit grep

215

grep -cDies ist nützlich, um festzustellen, wie oft eine Zeichenfolge in einer Datei vorkommt, zählt jedoch jedes Vorkommen nur einmal pro Zeile. Wie zähle ich mehrere Vorkommen pro Zeile?

Ich suche etwas eleganteres als:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
030
quelle
4
Ich weiß, dass grepes spezifiziert ist, aber für jeden ack, der es benutzt , ist die Antwort einfach ack -ch <pattern>.
Kyle Strand

Antworten:

302

greps -ogibt nur die Übereinstimmungen aus und ignoriert die Zeilen. wckann sie zählen:

grep -o 'needle' file | wc -l

Dies passt auch zu 'Nadeln' oder 'Mehrnadeln'.
Nur einzelne Wörter:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l
wedeln
quelle
6
Beachten Sie, dass hierfür GNU grep (Linux, Cygwin, FreeBSD, OSX) erforderlich ist.
Gilles
@wag Was zaubert \bund \Bmacht man hier?
Geek
6
@Geek \ b stimmt mit einer Wortgrenze überein, \ B stimmt NICHT mit einer Wortgrenze überein. Die obige Antwort wäre korrekter, wenn \ b an beiden Enden verwendet würde.
Liam
1
Kombinieren Sie die Anzahl der Vorkommen pro Zeile mit der Option grep -n und der Option uniq -c ... grep -no '\ <needle \>' file | uniq -c
jameswarren
@jameswarren uniqentfernt nur benachbarte identische Zeilen, die Sie sortvor dem Füttern entfernen müssen, uniqwenn Sie nicht bereits sicher sind, dass Duplikate immer unmittelbar benachbart sind.
Tripleee
16

Wenn Sie GNU grep haben (immer unter Linux und Cygwin, gelegentlich an anderer Stelle), können Sie die Ausgangsleitungen von zählengrep -o : grep -o needle | wc -l.

Mit Perl gibt es einige Möglichkeiten, die ich eleganter finde als Ihre (auch nachdem sie behoben wurden ).

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'

Wenn nur POSIX-Tools verwendet werden, besteht eine Möglichkeit darin, die Eingabe mit einer einzelnen Übereinstimmung in Zeilen aufzuteilen, bevor sie an grep übergeben wird. Wenn Sie beispielsweise nach ganzen Wörtern suchen, wandeln Sie zuerst jedes Nicht-Wort-Zeichen in eine neue Zeile um.

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

Andernfalls gibt es keinen Standardbefehl für diese spezielle Textverarbeitung. Sie müssen sich daher an sed (wenn Sie ein Masochist sind) oder awk wenden.

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

Hier ist eine einfachere Lösung mit sedund grep, die für Zeichenfolgen oder sogar reguläre Ausdrücke funktioniert, jedoch in einigen Eckfällen mit verankerten Mustern fehlschlägt (z. B. findet sie zwei Vorkommen von ^needleoder \bneedlein needleneedle).

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

Beachten Sie, dass ich in den obigen sed-Substitutionen \neinen Zeilenumbruch meinte. Dies ist Standard im Musterteil, aber im Ersetzungstext ersetzen Sie aus Gründen der Portabilität Backslash-Newline \n.

Gilles
quelle
4

Wenn Sie, wie ich, tatsächlich "beides, jedes genau einmal" wollten (dies ist tatsächlich "entweder; zweimal"), dann ist es ganz einfach:

grep -E "thing1|thing2" -c

und überprüfen Sie die Ausgabe 2.

Der Vorteil dieses Ansatzes (wenn genau einmal ist , was Sie wollen) ist , dass es leicht skaliert werden kann .

OJFord
quelle
Ich bin nicht sicher, ob Sie tatsächlich überprüfen, ob es nur einmal angezeigt wird. Alles, was Sie dort suchen, ist, dass eines dieser Wörter mindestens einmal vorkommt.
Steve Gore
3

Eine andere Lösung mit awk und needleals Feldtrenner:

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

Wenn Sie eine Übereinstimmung needlegefolgt von Interpunktion wünschen , ändern Sie das Feldtrennzeichen entsprechend

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

Oder verwenden Sie die Klasse [^[:alnum:]]:, um alle Nicht-Alpha-Zeichen einzuschließen.

ripat
quelle
Beachten Sie, dass hierfür ein awk erforderlich ist, das Regexp-Feldtrennzeichen (wie GNU awk) unterstützt.
Gilles
1

In Ihrem Beispiel wird nur die Anzahl der Vorkommen pro Zeile und nicht die Gesamtsumme in der Datei gedruckt. Wenn es das ist, was Sie wollen, könnte so etwas funktionieren:

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
jsbillings
quelle
Sie haben Recht - in meinem Beispiel werden nur die Vorkommen in der ersten Zeile gezählt.
1

Dies ist meine reine Bash-Lösung

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