Cách trích xuất chuỗi theo một mẫu với grep, regex hoặc perl


90

Tôi có một tệp trông giống như sau:

    <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>

Tôi cần trích xuất bất kỳ thứ gì trong dấu ngoặc kép theo sau name=, tức là content_analyzer, content_analyzer2content_analyzer_items.

Tôi đang thực hiện việc này trên hộp Linux, vì vậy giải pháp sử dụng sed, perl, grep hoặc bash là ổn.


5
không cần phải e ngại, chào mừng bạn ở đây!
Benoit

8
Tôi cảm thấy rằng nó sẽ là sai lầm không để liên kết đến stackoverflow.com/questions/1732348/...
Christoffer Hammarström

Cảm ơn mọi người vì những ý kiến ​​hữu ích. Tôi xin lỗi vì XML không được định dạng đúng. Tôi đã xóa một số thẻ để đơn giản hóa.
wrangler

Câu trả lời:


167

Vì bạn cần đối sánh nội dung mà không đưa nội dung đó vào kết quả (phải khớp name=" nhưng nó không phải là một phần của kết quả mong muốn) nên bắt buộc phải có một số hình thức đối sánh chiều rộng bằng 0 hoặc chụp nhóm. Điều này có thể được thực hiện dễ dàng với các công cụ sau:

Perl

Với Perl, bạn có thể sử dụng n tùy chọn để lặp lại từng dòng và in nội dung của một nhóm chụp nếu nó khớp:

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

GNU grep

Nếu bạn có phiên bản grep cải tiến, chẳng hạn như GNU grep, bạn có thể có -Ptùy chọn. Tùy chọn này sẽ kích hoạt Perl-like regex, cho phép bạn sử dụng \Kcái nhìn ngắn gọn. Nó sẽ đặt lại vị trí khớp, vì vậy bất kỳ thứ gì trước nó đều có độ rộng bằng không.

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

Các o làm cho tùy chọn grep chỉ in văn bản phù hợp, thay vì toàn bộ dòng.

Vim - Trình soạn thảo văn bản

Một cách khác là sử dụng trình soạn thảo văn bản trực tiếp. Với Vim, một trong những cách khác nhau để thực hiện điều này là xóa các dòng không có name=và sau đó trích xuất nội dung từ các dòng kết quả:

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

Grep tiêu chuẩn

Nếu bạn không có quyền truy cập vào các công cụ này, vì lý do nào đó, có thể đạt được điều gì đó tương tự với grep tiêu chuẩn. Tuy nhiên, nếu không xem xét xung quanh, nó sẽ yêu cầu một số dọn dẹp sau:

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

Lưu ý về việc lưu kết quả

Trong tất cả các lệnh trên, kết quả sẽ được gửi đến stdout. Điều quan trọng cần nhớ là bạn luôn có thể lưu chúng bằng cách chuyển nó vào một tệp bằng cách thêm:

> result

đến cuối lệnh.


12
Cách nhìn nhận (trong GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Tạm dừng cho đến khi có thông báo mới.

@Dennis Williamson, tuyệt vời. Tôi đã cập nhật câu trả lời cho phù hợp, nhưng để cả hai .*sang một bên, tôi hy vọng bạn không giận tôi. Tôi muốn hỏi, bạn có thấy lợi ích nào từ kết hợp không tham lam so với "bất cứ điều gì ngoại trừ "" không? Đừng coi đây là một cuộc chiến, tôi chỉ tò mò và tôi không phải là chuyên gia về regex. Ngoài ra, \Kmẹo, thực sự tốt đẹp. Cảm ơn Dennis.
sidyll

2
Tại sao tôi lại tức giận? Không có .*, bạn có thể làm grep -Po '(?<=name=").*?(?=")'. Có \Kthể được sử dụng cho tốc ký, nhưng nó thực sự chỉ cần thiết nếu khớp bên trái của nó có độ dài thay đổi. Trong những trường hợp như thế này, lý do của việc sử dụng cách nhìn chung là khá rõ ràng. Các thao tác không tự do trông gọn gàng hơn một chút ( [^"]*so với .*?và bạn không phải lặp lại ký tự neo. Tôi không biết về tốc độ. Điều đó phụ thuộc nhiều vào ngữ cảnh, tôi nghĩ. Tôi hy vọng điều đó hữu ích.
Tạm dừng cho đến khi có thông báo mới.

@Dennis Williamson: chắc chắn thưa ông, rất nhiều thông tin hữu ích ở đây. Tôi nghĩ lý do tôi giữ \K(sau khi nghiên cứu về nó) và loại bỏ nó .*là như nhau: làm cho nó trông đẹp (đơn giản hơn). Và tôi chưa bao giờ nghĩ đến việc sử dụng .*?thay vì "cách truyền thống" mà tôi đã học được từ đâu đó. Nhưng không tham lam ở đây thực sự có ý nghĩa. Cảm ơn Dennis, lời chúc tốt đẹp nhất.
sidyll

+1 để mô tả lệnh. Sẽ đánh giá cao nếu bạn có thể cập nhật câu trả lời của mình để giải thích phần "[...]" của regex.
lreeder

5

Biểu thức chính quy sẽ là:

.+name="([^"]+)"

Sau đó, nhóm sẽ nằm trong \ 1


5

Nếu bạn đang sử dụng Perl, hãy tải xuống một mô-đun để phân tích cú pháp XML: XML :: Simple , XML :: Twig , hoặc XML :: LibXML . Đừng phát minh lại bánh xe.


3
Lưu ý rằng ví dụ mà OP đưa ra không được định dạng tốt ( <type="global"chẳng hạn), vì vậy hầu hết các trình phân tích cú pháp XML chỉ phàn nàn và chết.
bvr

5

Một trình phân tích cú pháp HTML nên được sử dụng cho mục đích này hơn là các biểu thức chính quy. Một chương trình Perl sử dụng HTML::TreeBuilder:

Chương trình

#!/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>

Đầu ra

content_analyzer
content_analyzer2
content_analyzer_items

2

điều này có thể làm được:

perl -ne 'if(m/name="(.*?)"/){ print $1 . "\n"; }'

2

Đây là một giải pháp sử dụng HTML ngăn nắp & 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

Rất tiếc, lệnh sed dĩ nhiên phải đứng trước lệnh gọn gàng:

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

Nếu cấu trúc của xml (hoặc văn bản nói chung) của bạn được cố định, cách dễ nhất là sử dụng cut. Đối với trường hợp cụ thể của bạn:

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 '"'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.