นับจำนวนการเกิดทั้งหมดโดยใช้ grep


215

grep -cมีประโยชน์สำหรับการค้นหาจำนวนครั้งที่สตริงเกิดขึ้นในไฟล์ แต่นับเฉพาะแต่ละครั้งที่เกิดขึ้นหนึ่งครั้งต่อบรรทัด จะนับหลายครั้งต่อบรรทัดได้อย่างไร

ฉันกำลังมองหาบางสิ่งที่หรูหรากว่า:

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

4
ฉันรู้ว่าgrepมีการระบุ แต่สำหรับทุกคนที่ใช้คำตอบก็คือack ack -ch <pattern>
Kyle Strand

คำตอบ:


302

grep's -oจะส่งออกการแข่งขันโดยไม่สนใจเส้น; wcสามารถนับได้:

grep -o 'needle' file | wc -l

สิ่งนี้จะตรงกับ 'needles' หรือ 'multineedle'
คำเดียวเท่านั้น:

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

6
โปรดทราบว่าต้องใช้ grep GNU (Linux, Cygwin, FreeBSD, OSX)
Gilles

@wag ทำอะไรวิเศษ\bและ\Bทำที่นี่?
Geek

6
@Geek \ b ตรงกับขอบเขตของคำ \ B ไม่ตรงกับขอบเขตของคำ คำตอบข้างต้นจะถูกต้องมากขึ้นถ้าใช้ \ b ที่ปลายทั้งสอง
เลียม

1
สำหรับจำนวนครั้งที่เกิดขึ้นต่อบรรทัดรวมกับตัวเลือก grep -n และ uniq -c ... grep -no '\ <needle \>' file | uniq -c
jameswarren

@jameswarren uniqลบเฉพาะบรรทัดที่อยู่ติดกันคุณต้องsortก่อนที่จะให้อาหารuniqหากคุณยังไม่แน่ใจว่าซ้ำกันจะอยู่ติดกันทันที
tripleee

16

ถ้าคุณมี GNU grep (เสมอบน Linux และ Cygwin บางครั้งอื่น ๆ ) คุณสามารถนับสายออกจากgrep -ogrep -o needle | wc -l :

ด้วย Perl ต่อไปนี้เป็นวิธีการที่ฉันหาสวยกว่าของคุณมากขึ้น (แม้หลังจากที่แก้ไขแล้ว )

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'

ด้วยเครื่องมือ POSIX เพียงวิธีเดียวถ้าเป็นไปได้คือแยกอินพุตเป็นบรรทัดด้วยการจับคู่เดี่ยวก่อนส่งผ่านไปยัง grep ตัวอย่างเช่นหากคุณกำลังมองหาคำทั้งหมดให้เปลี่ยนอักขระที่ไม่ใช่คำทั้งหมดเป็นบรรทัดใหม่

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

มิฉะนั้นไม่มีคำสั่งมาตรฐานที่จะทำการประมวลผลข้อความบิตนี้โดยเฉพาะดังนั้นคุณต้องเปลี่ยนไปใช้ sed (ถ้าคุณเป็นนักทำโทษตนเอง) หรือ 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

ต่อไปนี้เป็นวิธีที่ง่ายกว่าในการใช้sedและgrepซึ่งใช้งานได้กับสตริงหรือนิพจน์ปกติโดยหนังสือ แต่ล้มเหลวในบางมุมที่มีรูปแบบการยึด (เช่นพบว่ามีสองเหตุการณ์เกิดขึ้น^needleหรือ\bneedleในneedleneedle)

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

โปรดทราบว่าในการแทนที่ sed ข้างต้นฉันเคย\nหมายถึงการขึ้นบรรทัดใหม่ นี่คือมาตรฐานในส่วนของรูปแบบ \nแต่ในข้อความทดแทนสำหรับการพกพาแทนทับขวาขึ้นบรรทัดใหม่สำหรับ


4

ถ้าเช่นฉันคุณต้องการ"ทั้งสอง; แต่ละครั้ง", (อันนี้คือ "ทั้งสอง;") แล้วมันง่าย:

grep -E "thing1|thing2" -c

2และตรวจสอบการส่งออก

ประโยชน์ของวิธีนี้ (ถ้าเพียงครั้งเดียวคือสิ่งที่คุณต้องการ) ก็คือมันปรับขนาดได้อย่างง่ายดาย


ฉันไม่แน่ใจว่าคุณกำลังตรวจสอบว่ามันปรากฏเพียงครั้งเดียวหรือไม่ สิ่งที่คุณกำลังมองหาคือมีคำใดคำหนึ่งที่มีอยู่อย่างน้อยหนึ่งครั้ง
Steve Gore

3

โซลูชันอื่นที่ใช้ awk และneedleตัวคั่นฟิลด์:

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

หากคุณต้องการจับคู่needleตามด้วยเครื่องหมายวรรคตอนให้เปลี่ยนตัวคั่นฟิลด์เช่น

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

หรือใช้คลาส: [^[:alnum:]]เพื่อรวมอักขระที่ไม่ใช่อัลฟาทั้งหมด


โปรดทราบว่านี่ต้องใช้ awk ที่รองรับตัวคั่นฟิลด์ regexp (เช่น GNU awk)
Gilles

1

ตัวอย่างของคุณพิมพ์จำนวนครั้งที่เกิดขึ้นต่อบรรทัดเท่านั้นไม่ใช่จำนวนทั้งหมดในไฟล์ ถ้านั่นคือสิ่งที่คุณต้องการบางอย่างเช่นนี้อาจทำงานได้:

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

คุณพูดถูก - ตัวอย่างของฉันนับเฉพาะสิ่งที่ปรากฏในบรรทัดแรก

1

นี่คือวิธีการทุบตีบริสุทธิ์ของฉัน

#!/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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.