ทุบตีหาบรรทัดที่ขึ้นต้นด้วยสตริง


10

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

ตัวอย่างเช่นสำหรับไฟล์ต่อไปนี้:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Cyyyyyyyyy
Czzzzzzzzz
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd
Ceeeeee

มีมากกว่าหนึ่งบรรทัดที่ขึ้นต้นด้วย 'C' ดังนั้นฉันต้องการให้ไฟล์นี้ถูกค้นพบโดยคำสั่ง
ตัวอย่างเช่นสำหรับไฟล์ต่อไปนี้:

Aaaaaaaaaaaa
Baaaaaaaaaaa
Cxxxxxxxxx
Abbbbbbbbbbb
Bbbbbbbbbbbb
Caaaaaa
Accccccccccc
Bccccccccccc
Cdddddd

มีหนึ่งบรรทัดที่ขึ้นต้นด้วย 'C' เสมอฉันไม่ต้องการไฟล์นี้ ฉันคิดว่าจะใช้ a grepหรือ a sedแต่ฉันไม่รู้วิธีการใช้ อาจใช้ regexp ^C.*$^Cหรืออะไรทำนองนั้น ความคิดใด ๆ


มีสองบรรทัดที่เริ่มต้นด้วยCในตัวอย่างที่สองของคุณ
cuonglm

5
คำถามนี้ไม่ชัดเจน คุณกำลังมองหาไฟล์ที่มีมากกว่าหนึ่งบรรทัดติดต่อกันที่ขึ้นต้นด้วยC?
แกรม

ใช่นี่คือสิ่งที่ฉันต้องการ ขอโทษที่เข้าใจผิด.
Jérémie

2
@terdon ดูเหมือนว่าการค้นหาแบบหลายบรรทัดด้วย -P ทำงานจนถึง 2.5.4 และไม่ใช่อีกต่อไปหลังจากนั้นแม้ว่าฉันจะไม่พบสิ่งใดในการเปลี่ยนแปลงที่จะอธิบายว่าทำไม
Stéphane Chazelas

1
@ Graeme คุณอาจต้องการยกเลิกการลบคำตอบของคุณดูความคิดเห็นของ Stephane เห็นได้ชัดว่ามันใช้งานได้กับgrepเวอร์ชั่นเก่ากว่านี้
terdon

คำตอบ:


5

ด้วยpcregrep:

pcregrep -rMl '^C.*\nC' .

POSIXly:

find . -type f -exec awk '
  FNR==1 {last=0; printed=0; next}
  printed {next}
  /^C/ {if (last) {print FILENAME; printed=1; nextfile} else last=1; next}
  {last=0}' {} +

(แม้ว่านั่นหมายถึงการอ่านไฟล์ทั้งหมดอย่างสมบูรณ์ด้วยawkการใช้งานที่ไม่สนับสนุนnextfile)


ด้วย GNU เวอร์ชันgrepสูงสุด 2.5.4:

grep -rlP '^C.*\nC' .

ดูเหมือนจะใช้งานได้ แต่เป็นไปโดยบังเอิญและไม่รับประกันว่าจะทำงาน

ก่อนที่มันจะได้รับการแก้ไขใน 2.6 (โดยการกระทำนี้ ) GNU grepได้มองข้ามว่าฟังก์ชั่นการค้นหาแบบ pcre ที่มันใช้จะจับคู่กับบัฟเฟอร์ทั้งหมดที่ประมวลผลในปัจจุบันโดยgrepทำให้เกิดพฤติกรรมแปลก ๆ ทุกประเภท ตัวอย่างเช่น

grep -P 'a\s*b'

จะจับคู่กับไฟล์ที่มี:

bla
bla

สิ่งนี้จะตรงกับ:

printf '1\n2\n' | grep -P '1\n2'

แต่นี่:

(printf '1\n'; sleep 1; printf '2\n') | grep -P '1\n2'

หรือ:

(yes | head -c 32766; printf '1\n2\n') > file; grep -P '1\n2' file

จะไม่ (ตามที่1\n2\nเป็นอยู่ในสองบัฟเฟอร์ที่ประมวลผลโดยgrep)

พฤติกรรมนั้นได้รับการบันทึกไว้ว่า:

15- ฉันจะจับคู่ข้ามเส้นได้อย่างไร

grep มาตรฐานไม่สามารถทำได้เนื่องจากเป็นพื้นฐานของบรรทัด ดังนั้นเพียงใช้คลาสอักขระ '[: space:]' ไม่ตรงกับบรรทัดใหม่ในแบบที่คุณอาจคาดหวัง อย่างไรก็ตามหาก grep ของคุณถูกคอมไพล์ด้วยรูปแบบ Perl ที่เปิดใช้งานตัวแก้ไข 'ของ Perl (ซึ่งทำขึ้นบรรทัดใหม่ที่ตรงกับ'. '):

     printf 'foo\nbar\n' | grep -P '(?s)foo.*?bar'

หลังจากแก้ไขแล้วใน 2.6 เอกสารไม่ได้ถูกแก้ไข (ฉันเคยรายงานไว้ที่นั่น )


มีเหตุผลที่จะไม่ใช้exitและ-exec \;แทนที่จะเป็น nextfile หรือไม่?
terdon

@terdon นั่นหมายถึงการรันหนึ่งawkไฟล์ต่อหนึ่งไฟล์ คุณต้องการทำเช่นนั้นก็ต่อเมื่อคุณawkไม่รองรับnextfileและคุณมีไฟล์ขนาดใหญ่ที่มีขนาดใหญ่และมีเส้นตรงเข้ามาที่จุดเริ่มต้นของไฟล์
Stéphane Chazelas

เทคนิค grepนี้เป็นอย่างไร(ฉันเดาว่ารุ่นล่าสุดของ GNU grep) ที่อำนวยความสะดวกในการจับคู่หลายบรรทัดโดยทำให้ไฟล์ทั้งหมดดูเหมือนเป็นสตริงเดียวโดยการตั้งค่า terminator ของบรรทัดเป็น NUL - คุณจะทราบหรือไม่ว่ามีข้อ จำกัด ใด ๆ
iruvar

1
@ 1_CR นั่นจะโหลดไฟล์ทั้งหมดในหน่วยความจำหากไม่มีตัวอักษร NUL อยู่ในนั้นและสมมติว่าบรรทัดไม่มีตัวอักษร NUL นอกจากนี้ทราบว่ารุ่นเก่าของ GNU grep (ซึ่ง OP มี) ไม่สามารถใช้กับ-z -Pไม่มีการ\Nโดยไม่ต้อง-Pคุณจะต้องเขียนมัน$'[\01-\011\013-\0377]'ซึ่งจะทำงานเฉพาะในสถานที่ C (ดูthread.gmane.org/gmane.comp.gnu.grep.bugs/5187 )
Stéphane Chazelas

@StephaneChazelas รายละเอียดที่มีประโยชน์มากขอบคุณ
iruvar

2

ด้วยawk:

awk '{if (p ~ /^C/ && $1 ~ /^C/) print; p=$1}' afile.txt

Cนี้จะพิมพ์เนื้อหาของแฟ้มถ้ามีสายติดต่อกันเริ่มต้นด้วย การแสดงออกจะมีลักษณะเป็นเส้นต่อเนื่องในแฟ้มและจะประเมินเป็นจริงถ้าตัวอักษรตัวแรกทั้งในการแข่งขัน(p ~ /^C/ && $1 ~ /^C/) Cหากเป็นกรณีนี้เส้นจะถูกพิมพ์

เพื่อค้นหาไฟล์ทั้งหมดที่มีรูปแบบดังกล่าวคุณสามารถเรียกใช้ awk ข้างต้นผ่านfindคำสั่ง:

find /your/path -type f -exec awk '{if (p ~ /^C/ && $1 ~ /^C/) {print FILENAME; exit;} p=$1}' {} \;

ในคำสั่งนี้เครื่องหมายfind+ execจะผ่านแต่ละไฟล์และดำเนินการคล้ายกันawkกรองที่ในแต่ละไฟล์และพิมพ์ชื่อผ่านFILENAMEถ้านิพจน์ awk ประเมินค่าเป็นจริง เพื่อหลีกเลี่ยงการพิมพ์FILENAMEหลายครั้งสำหรับไฟล์เดียวที่มีการจับคู่หลายexitคำสั่งจะถูกใช้ (ขอบคุณ @terdon)


คำถามของฉันไม่ชัดเจนพอฉันต้องการทราบชื่อของไฟล์ที่มีมากกว่าหนึ่งบรรทัดติดต่อกันเริ่มต้นด้วยC
Jérémie

@ Jérémieฉันได้รับคำตอบของฉันแล้ว
mkc

คุณช่วยเพิ่มคำอธิบายเกี่ยวกับการทำงานของมันได้ไหม นอกจากนี้ยังมีความจำเป็นในการไม่flagเพียงexitแทน ด้วยวิธีนี้คุณไม่จำเป็นต้องทำการประมวลผลไฟล์หลังจากพบคู่ที่ตรงกัน
terdon

2

อีกตัวเลือกหนึ่งกับ GNU sed:

สำหรับไฟล์เดียว:

sed -n -- '/^C/{n;/^C/q 1}' "$file" || printf '%s\n' "$file"

(แม้ว่ามันจะรายงานไฟล์ที่ไม่สามารถอ่านได้)

สำหรับfind:

find . -type f ! -exec sed -n '/^C/{n;/^C/q 1}' {} \; -print

ปัญหาเกี่ยวกับไฟล์ที่อ่านไม่ได้ที่กำลังพิมพ์สามารถหลีกเลี่ยงได้โดยการเขียนมัน:

find . -type f -size +2c -exec sed -n '$q1;/^C/{n;/^C/q}' {} \; -print

คุณช่วยอธิบายรายละเอียดได้sed -n '$q1;/^C/{n;/^C/q}'ไหม?
Jérémie

ใครจะอธิบายฉัน
Jérémie

@ Jérémie $q1- บังคับให้เลิกโดยมีข้อผิดพลาดหากไม่พบรูปแบบ มันจะจบด้วยข้อผิดพลาดหากมีสิ่งผิดปกติกับไฟล์ (ไม่สามารถอ่านได้หรือแตก) ดังนั้นมันจะออกจากสถานะ 0 ออกเฉพาะในกรณีที่พบรูปแบบและมันจะถูกส่งผ่านไปยังพิมพ์ ส่วนที่มี/^C/{n;/^C/qค่อนข้างง่าย หากพบสตริงที่ขึ้นต้นด้วย C มันจะอ่านบรรทัดถัดไปและหากมันเริ่มต้นด้วย C ก็จะเลิกด้วยสถานะออกเป็นศูนย์
เร่ง

1

สมมติว่าไฟล์ของคุณมีขนาดเล็กพอที่จะอ่านลงในหน่วยความจำ:

perl -000ne 'print "$ARGV\n" if /^C[^\n]*\nC/sm' *

คำอธิบาย:

  • - 000: ตั้ง\n\nเป็นตัวคั่นเรคคอร์ดโหมดนี้จะเปิดในย่อหน้าซึ่งจะจัดการกับย่อหน้า (คั่นด้วยการขึ้นบรรทัดใหม่ต่อเนื่อง) เป็นบรรทัดเดียว
  • -ne: ใช้สคริปต์ที่กำหนดเป็นอาร์กิวเมนต์ให้-eกับแต่ละบรรทัดของไฟล์อินพุต
  • $ARGV : เป็นไฟล์ที่กำลังถูกประมวลผล
  • /^C[^\n]*\nC/: การแข่งขันCที่จุดเริ่มต้นของบรรทัด (ดูคำอธิบายของsmการปรับเปลี่ยนด้านล่างสำหรับเหตุผลนี้ทำงานที่นี่) ตามด้วย 0 หรือมากกว่าไม่ใช่การขึ้นบรรทัดใหม่ตัวอักษรขึ้นบรรทัดใหม่และหลังจากนั้นอีกเซลเซียสในคำอื่น ๆ Cที่พบสายติดต่อกันเริ่มต้นด้วย * //sm: ตัวดัดแปลงการจับคู่เหล่านี้คือ (ดังที่บันทึกไว้ [ที่นี่]):

    • m : ถือว่าสตริงเป็นหลายบรรทัด นั่นคือเปลี่ยน "^" และ "$" จากการจับคู่จุดเริ่มต้นหรือจุดสิ้นสุดของบรรทัดที่ด้านซ้ายและขวาของสตริงเพื่อจับคู่กับที่ใดก็ได้ภายในสตริง

    • s : ถือว่าสตริงเป็นบรรทัดเดียว นั่นคือเปลี่ยน "." เพื่อจับคู่อักขระใด ๆ ก็ตามแม้กระทั่งการขึ้นบรรทัดใหม่ซึ่งโดยปกติจะไม่ตรงกัน

คุณสามารถทำสิ่งที่น่าเกลียดเช่น:

for f in *; do perl -pe 's/\n/%%/' "$f" | grep -q 'C[^%]*%%C' && echo "$f"; done

ที่นี่perlรหัสแทนการขึ้นบรรทัดใหม่ด้วย%%ดังนั้นสมมติว่าคุณไม่มี%%อยู่ในแฟ้มใส่ของคุณ (ใหญ่ถ้าของหลักสูตร) ที่จะตรงกับสายติดต่อกันเริ่มต้นด้วยgrepC


1

สารละลาย:

( set -- *files ; for f ; do (
set -- $(printf %c\  `cat <$f`)
while [ $# -ge 1 ] ;do [ -z "${1#"$2"}" ] && {
    echo "$f"; break ; } || shift
done ) ; done )

การสาธิต:

อันดับแรกเราจะสร้างฐานทดสอบ:

abc="a b c d e f g h i j k l m n o p q r s t u v w x y z" 
for l in $abc ; do { i=$((i+1)) h= c= ;
    [ $((i%3)) -eq 0 ] && c="$l" h="${abc%"$l"*}"
    line="$(printf '%s ' $h $c ${abc#"$h"})"
    printf "%s$(printf %s $line)\n" $line >|/tmp/file${i}
} ; done

ดังกล่าวข้างต้นจะสร้าง 26 ไฟล์ในชื่อ/tmp ในแต่ละไฟล์มี 27 หรือ 28 บรรทัดขึ้นต้นด้วยตัวอักษรและตามด้วยตัวอักษรที่เหลือ ทุกไฟล์ที่ 3 มีสองบรรทัดต่อเนื่องกันซึ่งอักขระตัวแรกถูกทำซ้ำfile1-26a-z

ตัวอย่าง:

cat /tmp/file12
...
aabcdefghijkllmnopqrstuvwxyz
babcdefghijkllmnopqrstuvwxyz
cabcdefghijkllmnopqrstuvwxyz
...
kabcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
labcdefghijkllmnopqrstuvwxyz
mabcdefghijkllmnopqrstuvwxyz
...

และเมื่อฉันเปลี่ยน:

set -- *files

ถึง:

set -- /tmp/file[0-9]*

ฉันเข้าใจ...

เอาท์พุท:

/tmp/file12
/tmp/file15
/tmp/file18
/tmp/file21
/tmp/file24
/tmp/file3
/tmp/file6
/tmp/file9

ดังนั้นโดยย่อการแก้ปัญหาทำงานดังนี้:

setระบุตำแหน่งย่อยของไฟล์ทั้งหมดของคุณและสำหรับแต่ละไฟล์

setsตำแหน่งย่อยของ subshell เป็นตัวอักษรตัวแรกของแต่ละบรรทัดในแต่ละไฟล์ขณะที่ลูป

[ tests ]ถ้า$1negates $2ระบุการแข่งขันและถ้าเป็นเช่นนั้น

echoesชื่อไฟล์นั้นbreakจะวนซ้ำปัจจุบัน

มิฉะนั้นshiftเป็นอักขระเดี่ยวตำแหน่งถัดไปเพื่อลองอีกครั้ง


0

สคริปต์นี้ใช้grepและcutรับหมายเลขบรรทัดของบรรทัดที่ตรงกันและตรวจสอบหมายเลขติดต่อกันสองรายการ ไฟล์จะถือว่าชื่อไฟล์ที่ถูกต้องถูกส่งผ่านเป็นอาร์กิวเมนต์แรกของสคริปต์:

#!/bin/bash

checkfile () {
 echo checking $1
 grep -n -E "^C.*$" $1 | cut -d: -f1 | while read linenum
     do
        : $[ ++PRV ] 
        if [ $linenum == $PRV ]; then return 1; fi
        PRV=$linenum
     done
     return 0
}

PRV="-1"
checkfile $1
if [ $? == 0 ]; then
   echo Consecutive matching lines found in file $1
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.