Policz całkowitą liczbę wystąpień za pomocą grep


215

grep -cprzydaje się do stwierdzenia, ile razy łańcuch występuje w pliku, ale zlicza każde wystąpienie tylko raz na linię. Jak policzyć wiele wystąpień w wierszu?

Szukam czegoś bardziej eleganckiego niż:

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

4
Wiem, że grepjest określony, ale dla każdego ack, kto używa , odpowiedź jest prosta ack -ch <pattern>.
Kyle Strand

Odpowiedzi:


302

grep wypisuje -otylko dopasowania, ignorując linie; wcmogę je policzyć:

grep -o 'needle' file | wc -l

Będzie to również pasować do „igieł” lub „igiełek”.
Tylko pojedyncze słowa:

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

6
Zauważ, że wymaga to GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles

@wag Jaką magię robi \bi \Brobi tutaj?
Geek

6
@Geek \ b dopasowuje granicę słów, \ B nie odpowiada granicy słów. Powyższa odpowiedź byłaby bardziej poprawna, gdyby używał \ b na obu końcach.
Liam,

1
Aby uzyskać liczbę wystąpień w wierszu, połącz z opcją grep -n i uniq -c ... grep -no '\ <igła \>' plik | uniq -c
jameswarren

@jameswarren uniqusuwa tylko sąsiadujące identyczne linie, musisz to zrobić sortprzed karmieniem, uniqjeśli nie jesteś już pewien, że duplikaty zawsze będą bezpośrednio sąsiadować.
tripleee

16

Jeśli masz GNU grep (zawsze na Linux i Cygwin, czasami gdzie indziej), można liczyć linie wyjściowe zgrep -o : grep -o needle | wc -l.

W Perlu jest kilka sposobów, które uważam za bardziej eleganckie niż twoje (nawet po ich naprawieniu ).

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'

W przypadku tylko narzędzi POSIX, jednym z podejść, jeśli to możliwe, jest podzielenie danych wejściowych na linie za pomocą jednego dopasowania przed przekazaniem go do grep. Na przykład, jeśli szukasz całych słów, najpierw zamień każdy znak niebędący znakiem na nowy wiersz.

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

W przeciwnym razie nie ma standardowego polecenia do wykonania tego konkretnego przetwarzania tekstu, więc musisz przejść do sed (jeśli jesteś masochistą) lub 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

Oto prostsze rozwiązanie wykorzystujące seda grep, który pracuje dla ciągów lub nawet by-the-book wyrażeń regularnych, ale nie powiedzie się w ciągu kilku przypadkach narożnych z zakotwiczonych wzorów (np znajdzie dwa wystąpienia ^needlelub \bneedlew needleneedle).

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

Zauważ, że w powyższych podstawieniach sed miałem \nna myśli nową linię. Jest to standardowe w części wzoru, ale w tekście zastępczym, dla przenośności, zamień na odwrotny ukośnik-nowa linia \n.


4

Jeśli, tak jak ja, tak naprawdę chciałeś „oba; każde dokładnie raz” (to właściwie „albo; dwa razy”), to jest to proste:

grep -E "thing1|thing2" -c

i sprawdź dane wyjściowe 2.

Zaletą tego podejścia (jeśli dokładnie raz jest to, czego chcesz) jest to, że łatwo się skaluje.


Nie jestem pewien, czy faktycznie sprawdzasz, czy pojawia się tylko raz? Wszystko, czego szukasz, to jedno z tych słów przynajmniej raz.
Steve Gore,

3

Inne rozwiązanie wykorzystujące awk i needlejako separator pól:

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

Jeśli chcesz dopasować, needlea następnie interpunkcję, zmień odpowiednio separator pól, tj

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

Lub użyj klasy [^[:alnum:]]:, aby objąć wszystkie znaki inne niż alfa.


Zauważ, że wymaga to awk, który obsługuje separatory wyrażeń regularnych (takich jak GNU awk).
Gilles

1

Twój przykład wypisuje tylko liczbę wystąpień na linię, a nie całkowitą liczbę w pliku. Jeśli tego właśnie chcesz, coś takiego może działać:

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

Masz rację - mój przykład liczy tylko wystąpienia w pierwszej linii.

1

To jest moje czyste rozwiązanie 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.