Đếm tổng số lần xuất hiện bằng grep


215

grep -crất hữu ích cho việc tìm kiếm bao nhiêu lần một chuỗi xảy ra trong một tệp, nhưng nó chỉ tính mỗi lần xuất hiện một lần trên mỗi dòng. Làm thế nào để đếm nhiều lần xuất hiện trên mỗi dòng?

Tôi đang tìm kiếm một cái gì đó thanh lịch hơn:

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

4
Tôi biết greplà được chỉ định, nhưng đối với bất cứ ai sử dụng ack, câu trả lời chỉ đơn giản là ack -ch <pattern>.
Kyle Strand

Câu trả lời:


302

grep's -osẽ chỉ xuất các trận đấu, bỏ qua các dòng; wccó thể đếm chúng:

grep -o 'needle' file | wc -l

Điều này cũng sẽ khớp với 'kim' hoặc 'multineedle'.
Chỉ một từ duy nhất:

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

6
Lưu ý rằng điều này đòi hỏi GNU grep (Linux, Cygwin, FreeBSD, OSX).
Gilles

@wag Phép thuật nào \b\Blàm ở đây?
Geek

6
@Geek \ b khớp với ranh giới từ, \ B không khớp với ranh giới từ. Câu trả lời ở trên sẽ đúng hơn nếu nó sử dụng \ b ở cả hai đầu.
Liam

1
Để biết số lần xuất hiện trên mỗi dòng, kết hợp với tùy chọn grep -n và uniq -c ... grep -no '\ <kim \>' | uniq -c
jameswarren

@jameswarren uniqchỉ xóa các dòng giống nhau liền kề, bạn cần phải sorttrước khi cho ăn uniqnếu bạn chưa chắc chắn rằng các bản sao sẽ luôn liền kề ngay lập tức.
tripleee

16

Nếu bạn có GNU grep (luôn có trên Linux và Cygwin, đôi khi ở nơi khác), bạn có thể đếm các dòng đầu ra từgrep -o : grep -o needle | wc -l.

Với Perl, đây là một vài cách tôi thấy thanh lịch hơn của bạn (ngay cả khi đã được sửa ).

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'

Chỉ với các công cụ POSIX, một cách tiếp cận, nếu có thể, là chia đầu vào thành các dòng với một khớp duy nhất trước khi chuyển nó sang grep. Ví dụ: nếu bạn đang tìm kiếm toàn bộ từ, thì trước tiên hãy biến mọi ký tự không phải từ thành một dòng mới.

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

Mặt khác, không có lệnh tiêu chuẩn để thực hiện xử lý văn bản cụ thể này, vì vậy bạn cần chuyển sang sed (nếu bạn là một masochist) hoặc 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

Đây là một giải pháp đơn giản hơn bằng cách sử dụng sedgrep, hoạt động cho các chuỗi hoặc thậm chí các biểu thức chính quy trong sách nhưng không thành công trong một vài trường hợp góc với các mẫu được neo (ví dụ: nó tìm thấy hai lần xuất hiện ^needlehoặc \bneedletrong needleneedle).

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

Lưu ý rằng trong các thay thế sed ở trên, tôi thường \ncó nghĩa là một dòng mới. Đây là tiêu chuẩn trong phần mẫu, nhưng trong văn bản thay thế, về tính di động, thay thế dấu gạch chéo ngược-newline cho \n.


4

Nếu, giống như tôi, bạn thực sự muốn "cả hai, mỗi lần chính xác một lần", (đây thực sự là "một trong hai lần") thì thật đơn giản:

grep -E "thing1|thing2" -c

và kiểm tra đầu ra 2.

Lợi ích của phương pháp này (nếu chính xác một lần những gì bạn muốn) là nó có quy mô dễ dàng.


Tôi không chắc chắn bạn thực sự kiểm tra nó chỉ xuất hiện một lần? Tất cả những gì bạn đang tìm kiếm là một trong những từ đó tồn tại ít nhất một lần.
Steve Gore

3

Một giải pháp khác sử dụng awk và needlephân tách trường:

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

Nếu bạn muốn khớp needletheo sau dấu chấm câu, hãy thay đổi dấu phân cách trường cho phù hợp

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

Hoặc sử dụng lớp: [^[:alnum:]]để bao gồm tất cả các ký tự không phải alpha.


Lưu ý rằng điều này đòi hỏi một awk hỗ trợ các dấu tách trường regrec (chẳng hạn như GNU awk).
Gilles

1

Ví dụ của bạn chỉ in ra số lần xuất hiện trên mỗi dòng chứ không phải tổng số trong tệp. Nếu đó là những gì bạn muốn, một cái gì đó như thế này có thể hoạt động:

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

Bạn đã đúng - ví dụ của tôi chỉ tính các lần xuất hiện trong dòng đầu tiên.

1

Đây là giải pháp bash tinh khiết của tôi

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.