Cara mengekstrak string mengikuti pola dengan grep, regex atau perl

90

Saya memiliki file yang terlihat seperti ini:

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

Saya perlu mengekstrak apa pun di dalam tanda kutip yang mengikuti name=, yaitu content_analyzer, content_analyzer2dan content_analyzer_items.

Saya melakukan ini di kotak Linux, jadi solusi menggunakan sed, perl, grep atau bash baik-baik saja.

penengkar
sumber
5
tidak perlu malu, selamat datang di sini!
Benoit
8
Saya merasa salah jika tidak menautkan ke stackoverflow.com/questions/1732348/…
Christoffer Hammarström
Terima kasih semuanya atas komentar yang bermanfaat. Saya minta maaf karena XML tidak diformat dengan benar. Saya menghapus beberapa tag untuk penyederhanaan.
penengkar

Jawaban:

167

Karena Anda perlu mencocokkan konten tanpa menyertakannya dalam hasil (harus cocok name=" tetapi bukan bagian dari hasil yang diinginkan), diperlukan beberapa bentuk pencocokan lebar-nol atau pengambilan kelompok. Ini dapat dilakukan dengan mudah dengan alat-alat berikut:

Perl

Dengan Perl Anda dapat menggunakan n opsi untuk memutar baris demi baris dan mencetak konten dari grup penangkap jika cocok:

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

GNU grep

Jika Anda memiliki versi grep yang ditingkatkan, seperti GNU grep, Anda mungkin memiliki -Popsi yang tersedia. Opsi ini akan mengaktifkan regex seperti Perl, memungkinkan Anda untuk menggunakan \Ktampilan singkat. Ini akan mengatur ulang posisi pertandingan, jadi apa pun sebelumnya adalah lebar nol.

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

The o merek pilihan grep mencetak hanya teks yang cocok, bukan seluruh baris.

Vim - Editor Teks

Cara lain adalah dengan menggunakan editor teks secara langsung. Dengan Vim, salah satu dari berbagai cara untuk menyelesaikan ini adalah dengan menghapus baris tanpa name=dan kemudian mengekstrak konten dari baris yang dihasilkan:

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

Grep standar

Jika Anda tidak memiliki akses ke alat ini, karena alasan tertentu, sesuatu yang serupa dapat dicapai dengan grep standar. Namun, tanpa melihat-lihat, ini akan membutuhkan pembersihan nanti:

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

Catatan tentang menyimpan hasil

Dalam semua perintah di atas, hasil akan dikirim ke stdout. Penting untuk diingat bahwa Anda selalu dapat menyimpannya dengan menyalurkannya ke file dengan menambahkan:

> result

ke akhir perintah.

sidyll
sumber
12
Lookarounds (di GNU grep):grep -Po '.*name="\K.*?(?=".*)'
Dijeda hingga pemberitahuan lebih lanjut.
@Dennis Williamson, bagus. Saya memperbarui jawabannya sesuai, tetapi .*mengesampingkan keduanya , saya harap Anda tidak marah kepada saya. Saya ingin bertanya, apakah Anda melihat manfaat dari pertandingan tidak serakah dibandingkan "apa pun kecuali ""? Jangan menganggap ini sebagai perkelahian, saya hanya ingin tahu dan saya bukan ahli regex. Juga, \Ktipnya, sangat bagus. Terima kasih Dennis.
sidyll
2
Mengapa saya marah? Tanpa itu .*, Anda bisa melakukannya grep -Po '(?<=name=").*?(?=")'. The \Kdapat digunakan untuk singkatan, tapi itu benar-benar hanya diperlukan jika pertandingan ke kiri adalah variabel panjang. Dalam kasus seperti ini, alasan menggunakan lookarounds cukup jelas. Operasi ungreedy terlihat sedikit lebih rapi ( [^"]*versus .*?dan Anda tidak perlu mengulangi karakter jangkar. Saya tidak tahu tentang kecepatan. Itu sangat tergantung pada konteksnya, saya rasa. Saya harap itu membantu.
Jeda sampai pemberitahuan lebih lanjut.
@Dennis Williamson: pasti pak, banyak informasi berguna disini. Saya pikir alasan saya menyimpan \K(setelah menelitinya) dan menghapusnya .*sama: membuatnya terlihat cantik (lebih sederhana). Dan saya tidak pernah berpikir untuk menggunakan .*?"cara tradisional" yang saya pelajari dari suatu tempat. Tapi tidak serakah di sini sangat masuk akal. Terima kasih Dennis, semoga sukses.
sidyll
1 untuk mendeskripsikan perintah. Akan sangat berterima kasih jika Anda dapat memperbarui jawaban Anda untuk menjelaskan bagian "[...]" dari regex.
lreeder
5

Ekspresi regulernya akan menjadi:

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

Kemudian pengelompokan akan berada di \ 1

Matt Shaver
sumber
5

Jika Anda menggunakan Perl, unduh modul untuk mengurai XML: XML :: Simple , XML :: Twig , atau XML :: LibXML . Jangan menemukan kembali roda.

shawnhcorey.dll
sumber
3
Perhatikan bahwa contoh yang diberikan OP tidak <type="global"dalam format yang baik ( misalnya), jadi sebagian besar pengurai XML hanya mengeluh dan mati.
bvr
5

Parser HTML harus digunakan untuk tujuan ini daripada ekspresi reguler. Program Perl yang memanfaatkan HTML::TreeBuilder:

Program

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

Keluaran

content_analyzer
content_analyzer2
content_analyzer_items
Alan Haggai Alavi
sumber
2

ini bisa melakukannya:

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

Berikut solusi menggunakan 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
mitma
sumber
1

Ups, perintah sed harus mendahului perintah rapi tentu saja:

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
mitma
sumber
0

Jika struktur xml Anda (atau teks pada umumnya) sudah diperbaiki, cara termudah adalah menggunakan cut. Untuk kasus spesifik Anda:

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 '"'
Carlos Lindado
sumber