วิธีการ grep สำหรับกลุ่มของตัวเลข n แต่ไม่เกิน n?


33

ฉันเรียนรู้ Linux และฉันมีความท้าทายที่ฉันไม่สามารถแก้ไขได้ด้วยตัวเอง นี่มันคือ:

grep บรรทัดจากไฟล์ที่มีตัวเลข 4 ตัวในแถว แต่ไม่เกิน 4

ฉันไม่แน่ใจว่าจะเข้าถึงสิ่งนี้ได้อย่างไร ฉันสามารถค้นหาตัวเลขเฉพาะ แต่ไม่ได้จำนวนในสตริง


2
ควรแสดงบรรทัดดัง1234a12345กล่าวหรือไม่?
Eliah Kagan

@Buddha คุณต้องอธิบายคำถามของคุณพร้อมกับตัวอย่าง
Avinash Raj

หากตัวเลขนำหน้าด้วยช่องว่างหรือจุดเริ่มต้นของเส้นยึดและตามด้วยช่องว่างหรือจุดสิ้นสุดของเส้นยึดคุณก็สามารถใช้ขอบเขตของคำได้ \b\d{4}\b
Avinash Raj

1
คำถามนี้แตกต่างจากคำถามบางข้อเกี่ยวกับนิพจน์ทั่วไปโดยชัดเจนเกี่ยวกับการใช้grep คำถามเกี่ยวกับการใช้ยูทิลิตี้ Unix ใน Ubuntu เช่น grep, sed และ awk ได้รับการพิจารณาว่าดีแล้ว บางครั้งผู้คนถามถึงวิธีการทำงานด้วยเครื่องมือที่ผิด ถ้าขาดบริบทก็เป็นปัญหาใหญ่ แต่นั่นไม่ใช่สิ่งที่เกิดขึ้นที่นี่ นี่คือหัวข้อชัดเจนเพียงพอที่จะตอบให้เป็นประโยชน์เป็นประโยชน์กับชุมชนของเราและไม่มีประโยชน์ในการป้องกันคำตอบเพิ่มเติมหรือผลักดันมันไปสู่การลบหรือการโยกย้าย ฉันโหวตให้เปิดใหม่อีกครั้ง
Eliah Kagan

1
ขอบคุณมากฉันไม่รู้ว่าฉันจะได้รับข้อเสนอแนะนี้มาก นี่คือคำตอบที่ฉันต้องการ: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])' ไฟล์ คำสั่งจะต้องสามารถดึงสตริงเช่นนี้ (ซึ่งเป็นเช่นนั้น): abc1234abcd99999
Buddha

คำตอบ:


52

มีสองวิธีในการตีความคำถามนี้ ฉันจะอยู่ทั้งสองกรณี คุณอาจต้องการแสดงบรรทัด:

  1. ที่มีลำดับของตัวเลขสี่หลักที่ไม่ได้เป็นส่วนหนึ่งของลำดับของตัวเลขอีกต่อไปหรือ
  2. ที่มีลำดับสี่หลัก แต่ไม่มีลำดับของตัวเลขอีกต่อไป (ไม่แยกกัน)

ตัวอย่างเช่น (1) จะแสดง1234a56789แต่ (2) จะไม่


หากคุณต้องการแสดงทุกบรรทัดที่มีลำดับของตัวเลขสี่หลักที่ไม่ได้เป็นส่วนหนึ่งของลำดับของตัวเลขอีกต่อไปทางเดียวคือ:

grep -P '(?<!\d)\d{4}(?!\d)' file

การใช้งานนี้นิพจน์ปกติ Perlซึ่งของ Ubuntu grep( GNU grep ) -Pสนับสนุนผ่านทาง มันจะไม่ตรงกับข้อความเช่น12345และจะไม่ตรงกับ1234หรือ2345เป็นส่วนหนึ่งของมัน แต่มันจะตรงกับใน12341234a56789

ในการแสดงออกปกติ Perl:

  • \dหมายถึงตัวเลขใด ๆ (มันเป็นวิธีสั้น ๆ ในการพูด[0-9]หรือ[[:digit:]])
  • x{4}ตรงกับx4 ครั้ง ( { }ไวยากรณ์ไม่ได้เฉพาะการแสดงออกปกติ Perl; มันอยู่ในการแสดงออกปกติขยายผ่านgrep -Eเช่นกัน.) ดังนั้นเป็นเช่นเดียวกับ\d{4}\d\d\d\d
  • (?<!\d)เป็นการยืนยันเชิงลบที่ดูเป็นศูนย์ มันหมายถึง "เว้นแต่นำหน้าด้วย\d"
  • (?!\d)เป็นการยืนยันล่วงหน้าที่เป็นค่าลบในการมองไปข้างหน้า มันหมายถึง "เว้นแต่ตามด้วย\d"

(?<!\d)และ(?!\d)ไม่ตรงกับข้อความนอกลำดับสี่หลัก แต่พวกเขาจะ (เมื่อใช้ร่วมกัน) ป้องกันไม่ให้ลำดับสี่หลักจากตัวเองถูกจับคู่ถ้ามันเป็นส่วนหนึ่งของลำดับของตัวเลขอีกต่อไป

การใช้เพียงการมองด้านหลังหรือการมองไปข้างหน้านั้นไม่เพียงพอเนื่องจากการเรียงลำดับตัวเลขสี่หลักทางขวาสุดหรือซ้ายสุดจะยังคงจับคู่อยู่

ประโยชน์อย่างหนึ่งของการใช้การมองด้านหลังและการยืนยันล่วงหน้าคือรูปแบบของคุณจะจับคู่กับตัวเลขสี่หลักเท่านั้นไม่ใช่ข้อความที่อยู่รอบ ๆ สิ่งนี้มีประโยชน์เมื่อใช้การเน้นสี (พร้อม--colorตัวเลือก)

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

โดยค่าเริ่มต้นในอูบุนตูผู้ใช้แต่ละคนมีalias grep='grep --color=auto'อยู่ในพวกเขาไฟล์~.bashrc ดังนั้นคุณจะได้รับการเน้นสีโดยอัตโนมัติเมื่อคุณเรียกใช้คำสั่งง่าย ๆ เริ่มต้นด้วยgrep(นี่คือเมื่อมีการขยายนามแฝง ) และเอาท์พุทมาตรฐานคือขั้ว (นี่คือสิ่งที่ตรวจสอบ) โดยทั่วไปแล้วการจับคู่จะถูกไฮไลต์ในเฉดสีแดง (ใกล้กับแดง ) แต่ฉันได้แสดงให้เห็นว่าเป็นตัวหนาตัวเอียง นี่คือภาพหน้าจอ:--color=auto
สกรีนช็อตแสดงคำสั่ง grep ที่มี 12345abc789d0123e4 เป็นเอาต์พุตโดยที่ 0123 เน้นด้วยสีแดง

และคุณสามารถgrepพิมพ์เฉพาะข้อความที่ตรงกันเท่านั้นและไม่ใช่ทั้งบรรทัดด้วย-o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

ทางเลือกโดยไม่ต้องมองไปข้างหลังและมองไปข้างหน้ายืนยัน

อย่างไรก็ตามหากคุณ:

  1. ต้องการคำสั่งที่จะทำงานบนระบบที่grepไม่รองรับ-Pหรือไม่ต้องการใช้นิพจน์ปกติของ Perl และ
  2. ไม่จำเป็นต้องจับคู่ตัวเลขสี่หลักโดยเฉพาะ - ซึ่งมักเป็นกรณีถ้าเป้าหมายของคุณคือแสดงบรรทัดที่มีการจับคู่และ
  3. ไม่เป็นไรกับวิธีแก้ปัญหาที่ค่อนข้างหรูหราน้อยลง

... จากนั้นคุณสามารถบรรลุสิ่งนี้ด้วยนิพจน์ทั่วไปที่ขยายเพิ่มแทน:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

ซึ่งตรงกับตัวเลขสี่หลักและอักขระที่ไม่ใช่ตัวเลข - หรือจุดเริ่มต้นหรือจุดสิ้นสุดของบรรทัด - ล้อมรอบพวกเขา โดยเฉพาะ:

  • [0-9]จับคู่ตัวเลขใด ๆ (เช่น[[:digit:]]หรือ\dในการแสดงออกปกติ Perl) และ{4}หมายถึง "สี่ครั้ง" เพื่อให้[0-9]{4}ตรงกับลำดับสี่หลัก
  • [^0-9]ตรงกับตัวอักษรที่ไม่ได้อยู่ในช่วงของผ่าน0 9มันเทียบเท่ากับ[^[:digit:]](หรือ\Dในการแสดงออกปกติ Perl)
  • ^เมื่อไม่ปรากฏใน[ ]เครื่องหมายวงเล็บให้จับคู่ส่วนต้นของบรรทัด ในทำนองเดียวกัน$ตรงกับจุดสิ้นสุดของบรรทัด
  • |หมายถึงหรือและวงเล็บคือสำหรับการจัดกลุ่ม (เช่นในพีชคณิต) ดังนั้น(^|[^0-9])ตรงกับจุดเริ่มต้นของบรรทัดหรืออักขระที่ไม่ใช่ตัวเลขในขณะที่($|[^0-9])ตรงกับจุดสิ้นสุดของบรรทัดหรืออักขระที่ไม่ใช่ตัวเลข

ดังนั้นการจับคู่เกิดขึ้นเฉพาะในบรรทัดที่มีลำดับตัวเลขสี่หลัก ( [0-9]{4}) ที่พร้อมกัน:

  • ที่จุดเริ่มต้นของบรรทัดหรือนำหน้าด้วยไม่ใช่ตัวเลข ( (^|[^0-9])) และ
  • ที่ท้ายบรรทัดหรือตามด้วยไม่ใช่ตัวเลข ( ($|[^0-9]))

ในทางกลับกันหากคุณต้องการแสดงทุกบรรทัดที่มีลำดับสี่หลัก แต่ไม่มีลำดับมากกว่าสี่หลักใด ๆ (แม้แต่อันที่แยกจากอีกสี่หลักเท่านั้น) จากนั้นให้แนวคิดของคุณ เป้าหมายคือการค้นหาบรรทัดที่ตรงกับรูปแบบหนึ่ง แต่ไม่ใช่แบบอื่น

ดังนั้นแม้ว่าคุณจะรู้วิธีการทำด้วยรูปแบบเดียวฉันขอแนะนำให้ใช้บางอย่างเช่นข้อเสนอแนะที่สองของแมตต์โดยgrepแยกเป็นสองรูปแบบ

คุณไม่ได้รับประโยชน์อย่างมากจากฟีเจอร์ขั้นสูงใด ๆ ของการแสดงออกปกติของ Perl เมื่อทำเช่นนั้นดังนั้นคุณอาจไม่ต้องการใช้มัน แต่เพื่อให้สอดคล้องกับสไตล์ข้างต้นนี่เป็นวิธีการแก้ปัญหาแบบสั้นโดยใช้\d(และวงเล็บปีกกา) แทน[0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

เพราะมันใช้[0-9], วิธีการของแมตต์เป็นแบบพกพาอื่น ๆ - มันจะทำงานบนระบบที่grepไม่สนับสนุนการแสดงออกปกติ Perl หากคุณใช้[0-9](หรือ[[:digit:]]) แทน\dแต่ยังคงใช้งานต่อไป{ }คุณจะได้รับความสะดวกในการพกพามากขึ้น:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

ทางเลือกด้วยรูปแบบเดียว

ถ้าคุณชอบgrepคำสั่งจริงๆ

  1. ใช้นิพจน์ปกติเดียว (ไม่ใช่สองgreps คั่นด้วยไพพ์ดังที่แสดงด้านบน)
  2. เพื่อแสดงบรรทัดที่มีสี่หลักอย่างน้อยหนึ่งรายการ
  3. แต่ไม่มีลำดับห้าหลัก (หรือมากกว่า)
  4. และคุณไม่รังเกียจที่จะจับคู่ทั้งบรรทัดไม่ใช่แค่ตัวเลข (คุณอาจไม่สนใจสิ่งนี้)

... จากนั้นคุณสามารถใช้:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

การ-xตั้งค่าสถานะทำให้grepแสดงเฉพาะบรรทัดที่ทั้งบรรทัดตรงกัน (แทนที่จะเป็นบรรทัดใด ๆที่มีการจับคู่)

ฉันใช้นิพจน์ปกติของ Perl เพราะฉันคิดว่าช่วงเวลาสั้น ๆ\dและ\Dเพิ่มความชัดเจนในกรณีนี้ แต่ถ้าคุณต้องการบางสิ่งบางอย่างแบบพกพาไปยังระบบที่grepไม่สนับสนุน-Pคุณสามารถแทนที่ด้วย[0-9]และ[^0-9](หรือด้วย[[:digit:]]และ[^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

วิธีการทำงานของนิพจน์ทั่วไปเหล่านี้คือ:

  • ตรงกลาง\d{4}หรือ[0-9]{4}ตรงกับหนึ่งในสี่หลัก เราอาจมีมากกว่าหนึ่งสิ่งเหล่านี้ แต่เราต้องมีอย่างน้อยหนึ่งอย่าง

  • ทางด้านซ้าย(\d{0,4}\D)*หรือ([0-9]{0,4}[^0-9])*ตรงกับ*อินสแตนซ์ศูนย์หรือมากกว่า ( ) ที่มีตัวเลขไม่เกินสี่หลักตามด้วยตัวเลขที่ไม่ใช่ตัวเลข ตัวเลขศูนย์ (กล่าวคือไม่มีอะไร) คือความเป็นไปได้อย่างหนึ่งสำหรับ "ไม่เกินสี่หลัก" การจับคู่นี้(a)สตริงว่างหรือ(b)สตริงใด ๆ ที่ลงท้ายด้วยไม่ใช่ตัวเลขและไม่มีลำดับใด ๆ ที่มีตัวเลขมากกว่าสี่หลัก

    เนื่องจากข้อความทางด้านซ้ายของกึ่งกลาง\d{4}(หรือ[0-9]{4}) ต้องว่างเปล่าหรือลงท้ายด้วยไม่ใช่ตัวเลขสิ่งนี้จะป้องกันไม่ให้ศูนย์กลาง\d{4}จับคู่สี่หลักที่มีอีก (ห้า) หลักทางด้านซ้ายของพวกเขา

  • ทางด้านขวา(\D\d{0,4})*หรือ([^0-9][0-9]{0,4})*ตรงกับ*อินสแตนซ์ที่เป็นศูนย์หรือมากกว่า ( ) ของไม่ใช่ตัวเลขตามด้วยตัวเลขไม่เกินสี่หลัก (ซึ่งเหมือนก่อนหน้านี้อาจเป็นสี่, สาม, สอง, หนึ่งหรือแม้กระทั่งไม่มีเลย) สิ่งนี้ตรงกับ(a)สตริงว่างหรือ(b)สตริงใด ๆ ที่เริ่มต้นด้วยตัวเลขที่ไม่ใช่ตัวเลขและไม่มีลำดับใด ๆ ที่มีตัวเลขมากกว่าสี่หลัก

    เนื่องจากข้อความทางด้านขวาของศูนย์กลาง\d{4}(หรือ[0-9]{4}) จะต้องว่างเปล่าหรือเริ่มต้นด้วยไม่ใช่ตัวเลขสิ่งนี้จะป้องกันไม่ให้ศูนย์กลาง\d{4}จับคู่สี่หลักที่มีอีก (ห้า) หลักทางด้านขวาของพวกเขา

สิ่งนี้ช่วยให้มั่นใจว่ามีลำดับสี่หลักอยู่ที่ใดที่หนึ่งและไม่มีลำดับของตัวเลขห้าหลักขึ้นไป

มันไม่เลวหรือผิดที่จะทำเช่นนี้ แต่บางทีเหตุผลที่สำคัญที่สุดในการพิจารณาทางเลือกนี้ก็คือมันช่วยให้ชัดเจนประโยชน์ของการใช้(หรือคล้ายกัน) แทนตามที่แนะนำข้างต้นและคำตอบของแมตต์grep -P '\d{4}' file | grep -Pv '\d{5}'

ด้วยวิธีดังกล่าวมันชัดเจนว่าเป้าหมายของคุณคือการเลือกบรรทัดที่มีสิ่งใดสิ่งหนึ่ง แต่ไม่ใช่สิ่งอื่น นอกจากนี้ไวยากรณ์ยังง่ายขึ้น (ดังนั้นผู้อ่าน / ผู้ดูแลรักษาหลายคนอาจเข้าใจได้เร็วขึ้น)


9

สิ่งนี้จะแสดงตัวเลข 4 ตัวติดต่อกัน แต่ไม่เกิน

grep '[0-9][0-9][0-9][0-9][^0-9]' file

หมายเหตุ ^ หมายถึงไม่

มีปัญหากับสิ่งนี้แม้ว่าฉันจะไม่แน่ใจว่าจะแก้ไขได้อย่างไร ... ถ้าจำนวนท้ายบรรทัดแล้วมันจะไม่ปรากฏขึ้น

รุ่น uglier นี้ แต่จะทำงานสำหรับกรณีที่

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]

โอ๊ะต้องไม่ได้ที่จะ egrep - ฉันได้แก้ไขมัน
แมตต์

2
คนแรกคือผิด - พบเพราะมันตรงกับa12345b 2345b
Volker Siegel

0

หากgrepไม่รองรับนิพจน์ทั่วไปของ Perl ( -P) ให้ใช้คำสั่ง shell ต่อไปนี้:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

ที่printf '[0-9]%.0s' {1..4}จะผลิต 4 [0-9]ครั้ง วิธีนี้มีประโยชน์เมื่อคุณมีตัวเลขยาวและคุณไม่ต้องการที่จะทำซ้ำรูปแบบ (เพียงแทนที่4ด้วยตัวเลขของคุณตัวเลขเพื่อค้นหา)

การใช้-wจะมองหาคำทั้งหมด อย่างไรก็ตามหากคุณสนใจสตริงตัวอักษรและตัวเลขเช่น1234aจากนั้นเพิ่ม[^0-9]ที่ส่วนท้ายของรูปแบบเช่น

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

ใช้$()เป็นพื้นแทนคำสั่ง ตรวจสอบโพสต์นี้เพื่อดูวิธีการprintfซ้ำรูปแบบ


0

คุณสามารถลองคำสั่งด้านล่างโดยแทนที่fileด้วยชื่อไฟล์จริงในระบบของคุณ:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

คุณสามารถตรวจสอบบทช่วยสอนนี้สำหรับการใช้คำสั่ง grep เพิ่มเติม

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