แยก regex ที่ตรงกับ 'sed' โดยไม่ต้องพิมพ์ตัวอักษรโดยรอบ


24

สำหรับแพทย์ 'sed' ทุกคนที่นั่น:

คุณจะทำให้ 'sed' แยกนิพจน์ปกติที่ตรงกันในบรรทัดได้อย่างไร

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

ฉันลองใช้คุณสมบัติการอ้างอิงกลับด้านล่าง

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

สิ่งนี้ใช้ได้กับบางนิพจน์เช่น

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

ซึ่งแยกชื่อแมโครทั้งหมดที่ขึ้นต้นด้วย 'CONFIG_ .... ' (พบในไฟล์ '* .h' บางไฟล์) และพิมพ์ออกมาทีละบรรทัด

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

แต่ข้างต้นแบ่งลงสำหรับสิ่งที่ต้องการ

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

การทำเช่นนี้จะส่งกลับตัวเลขหลักเดียวเสมอ

                 7
                 9
                 .
                 .  
                 6

แทนที่จะแยกเขตข้อมูลจำนวนที่อยู่ติดกันเช่น

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: ฉันจะขอบคุณคำติชมเกี่ยวกับวิธีการนี้จะประสบความสำเร็จใน 'sed' ฉันรู้วิธีการทำเช่นนี้กับ 'grep' และ 'awk' ฉันต้องการทราบว่าของฉัน - แม้ว่าจะมีข้อ จำกัด - ความเข้าใจใน 'sed' มีรูอยู่ในนั้นและหากมีวิธีการทำเช่นนี้ใน 'sed' ที่ฉัน
มี มองข้ามเพียงแค่

คำตอบ:


22

เมื่อ regexp มีกลุ่มอาจมีมากกว่าหนึ่งวิธีในการจับคู่สตริงกับมัน: regexps กับกลุ่มไม่ชัดเจน ยกตัวอย่างเช่นพิจารณา regexp และสตริง^.*\([0-9][0-9]*\)$ a12มีความเป็นไปได้สองอย่าง:

  • ตรงaกับ.*และ2ต่อต้าน[0-9]*; จะถูกจับคู่โดย1[0-9]
  • จับคู่a1กับ.*และสตริงที่ว่างเปล่ากับ[0-9]*; จะถูกจับคู่โดย2[0-9]

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

นี่คือการแข่งขันกับสตริงที่ยาวที่สุดเป็นครั้งแรกa1กับดังนั้นกลุ่มเดียวที่ตรงกับ.* 2หากคุณต้องการให้กลุ่มเริ่มต้นก่อนหน้านี้เครื่องยนต์ regexp บางตัวช่วยให้คุณ.*โลภน้อยลง แต่ยังไม่มีคุณสมบัติดังกล่าว ดังนั้นคุณต้องลบความกำกวมด้วยสมอเพิ่มเติมบางอย่าง ระบุว่าผู้นำ.*ไม่สามารถลงท้ายด้วยตัวเลขดังนั้นตัวเลขแรกของกลุ่มคือการจับคู่ที่เป็นไปได้ครั้งแรก

  • หากกลุ่มของตัวเลขไม่สามารถอยู่ที่จุดเริ่มต้นของบรรทัด:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • หากกลุ่มของตัวเลขสามารถอยู่ที่จุดเริ่มต้นของบรรทัดและ sed ของคุณสนับสนุน\?ผู้ประกอบการสำหรับชิ้นส่วนเพิ่มเติม:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • หากกลุ่มของตัวเลขสามารถอยู่ที่จุดเริ่มต้นของบรรทัดการผสานกับโครงสร้าง regexp มาตรฐาน:

    sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
    

โดยวิธีการที่มันเป็นเรื่องที่กฎแข่งขันที่เก่าแก่ที่สุดที่ยาวที่สุดที่ทำให้ตรงกับหลักหลังคนแรกมากกว่าต่อมา[0-9]*.*

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

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

โดยทั่วไปหากต้องการแยกคู่แรกของ regexp คุณจำเป็นต้องคำนวณการปฏิเสธของ regexp นั้น ในขณะที่สิ่งนี้เป็นไปได้ในทางทฤษฎีขนาดของการปฏิเสธจะเพิ่มขึ้นแบบทวีคูณเมื่อเทียบกับขนาดของ regexp ที่คุณกำลังปฏิเสธดังนั้นนี่จึงเป็นสิ่งที่ไม่จริง

ลองพิจารณาตัวอย่างอื่นของคุณ:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

ตัวอย่างนี้จริงแสดงปัญหาเดียวกัน แต่คุณไม่เห็นในอินพุตปกติ ถ้าคุณกินมันhello CONFIG_FOO_CONFIG_BARแล้วคำสั่งดังกล่าวพิมพ์ออกมาไม่ได้CONFIG_BARCONFIG_FOO_CONFIG_BAR

มีวิธีพิมพ์การจับคู่ครั้งแรกด้วย sed แต่มันค่อนข้างยุ่งยากเล็กน้อย:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(สมมติว่า sed ของคุณรองรับ\nเพื่อหมายถึงบรรทัดใหม่ในsข้อความแทนที่) การทำงานนี้เนื่องจาก sed ค้นหาการจับคู่ที่เร็วที่สุดของ regexp และเราจะไม่พยายามจับคู่สิ่งที่นำหน้าCONFIG_…บิต เนื่องจากไม่มีการขึ้นบรรทัดใหม่ภายในบรรทัดเราจึงสามารถใช้เป็นเครื่องหมายชั่วคราวได้ Tคำสั่งว่าจะให้ขึ้นถ้าก่อนหน้านี้sคำสั่งไม่ตรงกับ

เมื่อคุณไม่สามารถหาวิธีที่จะทำบางสิ่งบางอย่างใน sed ให้หันไป awk คำสั่งต่อไปนี้พิมพ์การจับคู่ที่ยาวที่สุดเร็วที่สุดของ regexp:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

และถ้าคุณรู้สึกอยากทำให้มันง่ายขึ้นให้ใช้ Perl

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match

22

แม้ว่าจะไม่ใช่sedสิ่งหนึ่งที่มักจะมองข้ามไปคือสิ่งนี้grep -oซึ่งในความคิดของฉันเป็นเครื่องมือที่ดีกว่าสำหรับงานนี้

ตัวอย่างเช่นหากคุณต้องการรับCONFIG_พารามิเตอร์ทั้งหมดจากเคอร์เนล config คุณจะใช้:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

หากคุณต้องการลำดับตัวเลขที่ต่อเนื่องกัน:

$ grep -Eo '[0-9]+' foo

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... จะทำสิ่งนี้โดยไม่ยุ่งยากแม้ว่าคุณอาจต้องการการขึ้นบรรทัดใหม่ตามตัวอักษรในnฟิลด์การเปลี่ยนตัวขวา และโดยวิธีการ.*CONFIGสิ่งที่จะทำงานเฉพาะถ้ามีเพียงหนึ่งการแข่งขันในบรรทัด - มันมิฉะนั้นจะได้รับเพียงครั้งสุดท้าย

คุณสามารถดูสิ่งนี้สำหรับคำอธิบายว่ามันทำงานอย่างไร แต่สิ่งนี้จะพิมพ์บนบรรทัดที่แยกจากกันเฉพาะการแข่งขันหลายครั้งเท่าที่มันเกิดขึ้นบนบรรทัด

คุณสามารถใช้กลยุทธ์เดียวกันเพื่อให้[num]เกิดเหตุการณ์ที่บรรทัด ตัวอย่างเช่นหากคุณต้องการพิมพ์ CONFIG ที่ตรงกันหากเป็นรายการที่สามในบรรทัด:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... แม้ว่าจะถือว่าCONFIGสตริงถูกคั่นด้วยอักขระที่ไม่ใช่ตัวอักษรและตัวเลขอย่างน้อยหนึ่งตัวสำหรับแต่ละเหตุการณ์

ฉันคิดว่า - สำหรับสิ่งที่มีจำนวน - สิ่งนี้จะได้ผล:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... \nมีข้อแม้เดียวกับก่อนที่จะเกี่ยวกับทางด้านขวามือ อันนี้จะเร็วกว่าครั้งแรก แต่ไม่สามารถใช้ตามปกติได้อย่างชัดเจน

สำหรับสิ่ง CONFIG คุณสามารถใช้P;...;Dลูปด้านบนกับรูปแบบของคุณหรือคุณสามารถทำได้:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... ซึ่งเกี่ยวข้องเพียงเล็กน้อยและทำงานได้โดยการเรียงsedลำดับความสำคัญของการอ้างอิงอย่างถูกต้อง นอกจากนี้ยังแยก CONFIG ที่ตรงกันทั้งหมดบนบรรทัดในครั้งเดียว - แม้ว่ามันจะทำให้สมมติฐานเดียวกันเหมือนก่อนว่า - การจับคู่แต่ละ CONFIG จะถูกคั่นด้วยอักขระที่ไม่ใช่ตัวอักษรและตัวเลขอย่างน้อยหนึ่งตัว ด้วย GNU sedคุณสามารถเขียนมันได้:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.