Hitung jumlah total kejadian menggunakan grep

215

grep -cberguna untuk menemukan berapa kali string muncul dalam file, tetapi hanya menghitung setiap kemunculannya sekali per baris. Bagaimana cara menghitung beberapa kejadian per baris?

Saya mencari sesuatu yang lebih elegan daripada:

perl -e '$_ = <>; print scalar ( () = m/needle/g ), "\n"'
030
sumber
4
Saya tahu grepditentukan, tetapi bagi siapa pun yang menggunakan ack, jawabannya sederhana ack -ch <pattern>.
Kyle Strand

Jawaban:

302

grep's -ohanya akan menampilkan pertandingan, mengabaikan garis; wcdapat menghitungnya:

grep -o 'needle' file | wc -l

Ini juga akan cocok dengan 'jarum' atau 'multineedle'.
Hanya satu kata:

grep -o '\bneedle\B' file | wc -l
# or:
grep -o '\<needle\>' file | wc -l
mengibaskan
sumber
6
Perhatikan bahwa ini memerlukan GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles
@wag Sihir apa yang dilakukan \bdan \Bdilakukan di sini?
Geek
6
@Geek \ b cocok dengan batas kata, \ B cocok dengan TIDAK batas kata. Jawaban di atas akan lebih benar jika digunakan \ b di kedua ujungnya.
Liam
1
Untuk hitungan kemunculan per baris, gabungkan dengan opsi grep -n dan uniq -c ... grep -tidak ada file '\ <needle \>' | uniq -c
jameswarren
@jameswarren uniqhanya menghapus garis identik yang berdekatan, Anda perlu sortsebelum memberi makan ke uniqjika Anda belum yakin bahwa duplikat akan selalu berbatasan langsung.
tripleee
16

Jika Anda memiliki GNU grep (selalu di Linux dan Cygwin, kadang-kadang di tempat lain), Anda dapat menghitung baris output darigrep -o : grep -o needle | wc -l.

Dengan Perl, berikut adalah beberapa cara yang saya anggap lebih elegan dari milik Anda (bahkan setelah diperbaiki ).

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'

Dengan hanya alat POSIX, satu pendekatan, jika mungkin, adalah membagi input menjadi garis dengan satu kecocokan sebelum meneruskannya ke grep. Misalnya, jika Anda mencari seluruh kata, maka pertama-tama ubah setiap karakter non-kata menjadi baris baru.

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

Kalau tidak, tidak ada perintah standar untuk melakukan sedikit pemrosesan teks ini, jadi Anda perlu beralih ke sed (jika Anda seorang masokis) atau 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

Berikut adalah solusi yang lebih sederhana menggunakan seddan grep, yang berfungsi untuk string atau bahkan oleh-the-book ekspresi reguler tetapi gagal dalam beberapa kasus sudut dengan pola berlabuh (misalnya menemukan dua kemunculan ^needleatau \bneedledalam needleneedle).

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

Perhatikan bahwa dalam substitusi sed di atas, yang saya \nmaksud adalah baris baru. Ini adalah standar di bagian pola, tetapi dalam teks pengganti, untuk portabilitas, gantikan backslash-newline untuk \n.

Gilles
sumber
4

Jika, seperti saya, Anda benar-benar ingin "keduanya; masing-masing tepat sekali", (ini sebenarnya "baik; dua kali") maka itu sederhana:

grep -E "thing1|thing2" -c

dan periksa hasilnya 2.

Manfaat dari pendekatan ini (jika tepat sekali adalah apa yang Anda inginkan) adalah mudah untuk diukur.

OJFord
sumber
Saya tidak yakin Anda benar-benar memeriksa itu hanya muncul sekali? Yang Anda cari di sana adalah bahwa salah satu dari kata-kata itu ada setidaknya satu kali.
Steve Gore
3

Solusi lain menggunakan awk dan needlesebagai pemisah bidang:

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

Jika Anda ingin mencocokkan needlediikuti oleh tanda baca, ubah pemisah bidang yang sesuai yaitu

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

Atau gunakan kelas: [^[:alnum:]]untuk mencakup semua karakter non alpha.

ripat
sumber
Perhatikan bahwa ini membutuhkan awk yang mendukung pemisah bidang regexp (seperti awk GNU).
Gilles
1

Contoh Anda hanya mencetak jumlah kemunculan per-baris, dan bukan total dalam file. Jika itu yang Anda inginkan, sesuatu seperti ini mungkin berhasil:

perl -nle '$c+=scalar(()=m/needle/g);END{print $c}' 
jsbillings
sumber
Anda benar - contoh saya hanya menghitung kejadian di baris pertama.
1

Ini adalah solusi bash murni saya

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