วิธีการแยกสตริงตามรูปแบบด้วย grep, regex หรือ perl


91

ฉันมีไฟล์ที่มีลักษณะดังนี้:

    <table name="content_analyzer" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer2" primary-key="id">
      <type="global" />
    </table>
    <table name="content_analyzer_items" primary-key="id">
      <type="global" />
    </table>

ฉันต้องการที่จะดึงอะไรที่อยู่ในคำพูดที่ว่าต่อไปนี้name=คือcontent_analyzer, และcontent_analyzer2content_analyzer_items

ฉันกำลังทำสิ่งนี้บนกล่อง Linux ดังนั้นวิธีแก้ปัญหาโดยใช้ sed, perl, grep หรือ bash ก็ใช้ได้


5
ไม่ต้องอายยินดีต้อนรับที่นี่!
Benoit

8
ฉันรู้สึกว่ามันไม่ถูกต้องที่จะไม่เชื่อมโยงไปยังstackoverflow.com/questions/1732348/…
Christoffer Hammarström

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

คำตอบ:


172

เนื่องจากคุณต้องจับคู่เนื้อหาโดยไม่รวมเนื้อหานั้นไว้ในผลลัพธ์ (ต้องตรงname=" แต่ไม่ใช่ส่วนหนึ่งของผลลัพธ์ที่ต้องการ) จำเป็นต้องมีรูปแบบการจับคู่ความกว้างศูนย์หรือการจับภาพกลุ่มบางรูปแบบ สามารถทำได้อย่างง่ายดายด้วยเครื่องมือต่อไปนี้:

Perl

ด้วย Perl คุณสามารถใช้n ตัวเลือกเพื่อวนซ้ำทีละบรรทัดและพิมพ์เนื้อหาของกลุ่มการจับภาพหากตรงกับ:

perl -ne 'print "$1\n" if /name="(.*?)"/' filename

GNU grep

หากคุณมี grep เวอร์ชันที่ปรับปรุงแล้วเช่น GNU grep คุณอาจมี -Pตัวเลือกให้ ตัวเลือกนี้จะเปิดใช้ regex เหมือน Perl ทำให้คุณสามารถใช้\Kซึ่งเป็นการมองชวเลขที่อยู่เบื้องหลัง มันจะรีเซ็ตตำแหน่งการจับคู่ดังนั้นสิ่งใดก็ตามก่อนที่จะมีความกว้างเป็นศูนย์

grep -Po 'name="\K.*?(?=")' filename

o ทำให้ตัวเลือก grep พิมพ์เฉพาะข้อความที่ตรงกันแทนของสายทั้ง

Vim - โปรแกรมแก้ไขข้อความ

อีกวิธีหนึ่งคือการใช้โปรแกรมแก้ไขข้อความโดยตรง ด้วย Vim หนึ่งในวิธีต่างๆในการทำสิ่งนี้คือการลบบรรทัดโดยไม่ต้อง name=แล้วแยกเนื้อหาออกจากบรรทัดผลลัพธ์:

:v/.*name="\v([^"]+).*/d|%s//\1

grep มาตรฐาน

หากคุณไม่สามารถเข้าถึงเครื่องมือเหล่านี้ด้วยเหตุผลบางประการสิ่งที่คล้ายกันนี้สามารถทำได้ด้วย grep มาตรฐาน อย่างไรก็ตามหากไม่มีการมองไปรอบ ๆ จะต้องมีการล้างข้อมูลในภายหลัง:

grep -o 'name="[^"]*"' filename

หมายเหตุเกี่ยวกับการบันทึกผลลัพธ์

stdoutในทุกคำสั่งดังกล่าวผลจะถูกส่งไป สิ่งสำคัญคือต้องจำไว้ว่าคุณสามารถบันทึกได้ตลอดเวลาโดยการลากไปที่ไฟล์โดยต่อท้าย

> result

ไปยังจุดสิ้นสุดของคำสั่ง


12
Lookarounds (ใน GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Dennis Williamson

@ เดนนิสวิลเลียมสันเยี่ยมมาก ฉันอัปเดตคำตอบตามนั้น แต่ปล่อย.*ไว้ทั้งสองอย่างฉันหวังว่าคุณจะไม่โกรธฉัน ฉันอยากจะถามว่าคุณเห็นประโยชน์จากการจับคู่แบบไม่โลภมากกว่า "อะไรก็ได้ยกเว้น"" หรือไม่? อย่าใช้เรื่องนี้เป็นการต่อสู้ฉันแค่อยากรู้อยากเห็นและฉันไม่ใช่ผู้เชี่ยวชาญด้าน regex นอกจากนี้\Kเคล็ดลับดีจริงๆ ขอบคุณเดนนิส
sidyll

2
จะโกรธทำไม โดยไม่ต้องคุณสามารถทำได้.* สามารถใช้สำหรับการจดชวเลข แต่มันจำเป็นจริงๆเท่านั้นหากการแข่งขันทางด้านซ้ายของมันคือความยาวของตัวแปร ในกรณีเช่นนี้เหตุผลในการใช้ Lookarounds ค่อนข้างชัดเจน การดำเนินการที่ไม่น่ากลัวดูเรียบง่ายกว่าเล็กน้อย ( เทียบกับและคุณไม่จำเป็นต้องทำซ้ำตัวละครหลักฉันไม่รู้เรื่องความเร็วนั่นขึ้นอยู่กับบริบทมากฉันคิดว่าฉันหวังว่าจะเป็นประโยชน์grep -Po '(?<=name=").*?(?=")'\K[^"]*.*?
Dennis Williamson

@ เดนนิสวิลเลียมสัน: ครับข้อมูลที่เป็นประโยชน์มากมายที่นี่ ฉันคิดว่าเหตุผลที่ฉันเก็บไว้\K(หลังจากค้นคว้าเกี่ยวกับเรื่องนี้) และลบออก.*ก็เหมือนเดิม: ทำให้มันดูสวย (ง่ายกว่า) และฉันไม่เคยคิดที่จะใช้.*?แทน "วิธีดั้งเดิม" ที่ฉันเรียนรู้จากที่ไหนสักแห่ง แต่การไม่โลภนี่สมเหตุสมผลจริงๆ ขอบคุณเดนนิสด้วยความปรารถนาดี
sidyll

+1 สำหรับอธิบายคำสั่ง จะขอบคุณมากถ้าคุณสามารถอัปเดตคำตอบของคุณเพื่ออธิบายส่วน "[... ]" ของ regex
lreeder


5

หากคุณกำลังใช้ Perl ดาวน์โหลดโมดูลเพื่อแยก XML: XML :: ง่าย , XML :: ทวิหรือXML :: libxml อย่าประดิษฐ์ล้อใหม่


3
โปรดทราบว่าตัวอย่าง OP ที่ให้นั้นไม่ได้มีรูปแบบที่ดี ( <type="global"เช่น) ดังนั้นตัวแยกวิเคราะห์ XML ส่วนใหญ่จึงบ่นและตาย
bvr

5

ควรใช้ตัวแยกวิเคราะห์ HTML เพื่อจุดประสงค์นี้แทนที่จะใช้นิพจน์ทั่วไป โปรแกรม Perl ที่ใช้ประโยชน์จากHTML::TreeBuilder:

โปรแกรม

#!/usr/bin/env perl

use strict;
use warnings;

use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new_from_file( \*DATA );
my @elements = $tree->look_down(
    sub { defined $_[0]->attr('name') }
);

for (@elements) {
    print $_->attr('name'), "\n";
}

__DATA__
<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>

เอาต์พุต

content_analyzer
content_analyzer2
content_analyzer_items


2

นี่คือวิธีแก้ปัญหาโดยใช้ HTML tidy & xmlstarlet:

htmlstr='
<table name="content_analyzer" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
<type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
<type="global" />
</table>
'

echo "$htmlstr" | tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
sed '/type="global"/d' |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

1

อ๊ะคำสั่ง sed ต้องนำหน้าคำสั่งที่เป็นระเบียบเรียบร้อยแน่นอน:

echo "$htmlstr" | 
sed '/type="global"/d' |
tidy -q -c -wrap 0 -numeric -asxml -utf8 --merge-divs yes --merge-spans yes 2>/dev/null |
xmlstarlet sel -N x="http://www.w3.org/1999/xhtml" -T -t -m "//x:table" -v '@name' -n

0

หากโครงสร้างของ XML ของคุณ (หรือข้อความทั่วไป) cutได้รับการแก้ไขด้วยวิธีที่ง่ายที่สุดคือการใช้ สำหรับกรณีเฉพาะของคุณ:

echo '<table name="content_analyzer" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer2" primary-key="id">
  <type="global" />
</table>
<table name="content_analyzer_items" primary-key="id">
  <type="global" />
</table>' | grep name= | cut -f2 -d '"'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.