Menemukan teks di antara dua karakter atau string tertentu

17

Katakanlah saya memiliki garis seperti ini:

*[234]*
*[23]*
*[1453]*

di mana *mewakili string apa pun (kecuali string bentuk [number]). Bagaimana saya bisa menguraikan baris-baris ini dengan utilitas baris perintah dan mengekstrak angka di antara tanda kurung?

Lebih umum, yang dari alat ini cut, sed, grepatau awkakan sesuai untuk tugas seperti itu?

Amelio Vazquez-Reina
sumber

Jawaban:

16

Jika Anda memiliki GNU grep, Anda dapat menggunakan -oopsi untuk mencari regex dan hanya menghasilkan bagian yang cocok. (Implementasi grep lainnya hanya dapat menampilkan seluruh baris.) Jika ada beberapa kecocokan pada satu baris, mereka dicetak pada garis yang berbeda.

grep -o '\[[0-9]*\]'

Jika Anda hanya menginginkan angka dan bukan tanda kurung, itu sedikit lebih sulit; Anda perlu menggunakan pernyataan nol-lebar: regexp yang cocok dengan string kosong, tetapi hanya jika didahului, atau diikuti seperti kasusnya, dengan braket. Pernyataan nol-lebar hanya tersedia dalam sintaks Perl.

grep -P -o '(?<=\[)[0-9]*(?=\])'

Dengan sed, Anda harus mematikan pencetakan -n, dan mencocokkan seluruh garis dan hanya mempertahankan bagian yang cocok. Jika ada beberapa kemungkinan kecocokan pada satu baris, hanya kecocokan terakhir yang dicetak. Lihat Mengekstrak regex yang cocok dengan 'sed' tanpa mencetak karakter di sekitarnya untuk detail lebih lanjut tentang penggunaan sed di sini.

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

atau jika Anda hanya menginginkan angka dan bukan tanda kurung:

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

Tanpa grep -o, Perl adalah alat pilihan di sini jika Anda menginginkan sesuatu yang sederhana dan mudah dipahami. Di setiap baris ( -n), jika baris berisi kecocokan untuk \[[0-9]*\], maka cetak kecocokan itu ( $&) dan baris baru ( -l).

perl -l -ne '/\[[0-9]*\]/ and print $&'

Jika Anda hanya menginginkan digit, letakkan tanda kurung di regex untuk membatasi grup, dan cetak hanya grup itu.

perl -l -ne '/\[([0-9]*)\]/ and print $1'

PS Jika Anda hanya ingin meminta satu angka atau lebih di antara tanda kurung, ubah [0-9]*ke [0-9][0-9]*, atau ke [0-9]+dalam Perl.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Semua bagus, selain itu dia ingin "mengekstrak angka di antara tanda kurung". Saya pikir "kecuali [number]" berarti kecuali[0-9]
Peter.O
1
@ Peter.OI mengerti "kecuali [angka]" berarti bahwa tidak ada bagian lain dari garis formulir itu. Tetapi saya mengedit jawaban saya untuk menunjukkan cara mencetak hanya digit, untuk berjaga-jaga.
Gilles 'SO- stop being evil'
1
Pernyataan perlregex tersebut terlihat sangat berguna! Saya telah membaca tentang mereka setelah melihat Anda menggunakan pernyataan mundur dan maju, bahkan dalam grep (saya telah mematikan fakta Anda dapat memilih mesin regex). Saya akan mencurahkan sedikit lebih banyak waktu untuk perl's regex mulai dari sini. Terima kasih ... PS .. Saya baru saja membaca man grep... "Ini sangat eksperimental dan grep -P dapat memperingatkan fitur yang tidak diimplementasikan." ... Saya harap itu tidak berarti tidak stabil (?) ...
Peter.O
5

Anda tidak dapat melakukannya dengan cut.

  1. tr -c -d '0123456789\012'
  2. sed 's/[^0-9]*//g'
  3. awk -F'[^0-9]+' '{ print $1$2$3 }'
  4. grep -o -E '[0-9]+'

tr adalah solusi paling alami untuk masalah ini dan mungkin akan berjalan paling cepat, tetapi saya pikir Anda akan membutuhkan input raksasa untuk memisahkan salah satu opsi ini dalam hal kecepatan.

Kyle Jones
sumber
Untuk sed, ^.*apakah serakah dan mengkonsumsi semua kecuali digit terakhir, dan +perlu \+atau menggunakan posix \([0-9][0-9]*\).... dan dalam hal apapun 's/[^0-9]*//g'bekerja dengan baik, ... Thanks for the contoh tr -c`, tetapi bukankah itu trailing \012surperfluous?
Peter.O
@ Peter Terima kasih telah menangkap itu. Saya bersumpah telah menguji contoh sed. :( Saya sudah mengubahnya ke versi Anda. Mengenai \012: diperlukan jika tidak trakan memakan baris baru.
Kyle Jones
Aha ... Aku melihatnya sebagai \0, 1, 2(atau bahkan \, 0, 1, 2). Saya tidak cukup terbiasa dengan oktal sepertinya .. Terima kasih.
Peter.O
4

Jika Anda bermaksud mengekstraksi serangkaian digit berturut-turut antara karakter non-digit, saya kira seddan awkadalah yang terbaik (walaupun grepjuga dapat memberi Anda karakter yang cocok):

sed: Anda tentu saja dapat mencocokkan digit, tetapi mungkin menarik untuk melakukan yang sebaliknya, menghapus non-digit (berfungsi sejauh hanya ada satu angka per baris):

$ echo nn3334nn | sed -e 's/[^[[:digit:]]]*//g'
3344

grep: Anda dapat mencocokkan digit berurutan

$ echo nn3334nn | grep -o '[[:digit:]]*'
3344

Saya tidak memberikan contoh awkkarena saya memiliki pengalaman nol dengannya; Sangat menarik untuk dicatat bahwa, meskipun sedpisau swiss, grepmemberi Anda cara yang lebih sederhana dan lebih mudah dibaca untuk melakukan ini, yang juga berfungsi untuk lebih dari satu angka pada setiap jalur input (yang -ohanya mencetak bagian yang cocok dari input, masing-masing pada jalurnya sendiri):

$ echo dna42dna54dna | grep -o '[[:digit:]]*'
42
54
njsg
sumber
Hanya sebagai perbandingan, di sini adalah sedeqivalent dari "lebih dari satu nomor per baris" misalnya grep -o '[[:digit:]]*'. . . sed -nr '/[0-9]/{ s/^[^[0-9]*|[^0-9]*$//g; s/[^0-9]+/\n/g; p}'... (+1)
Peter.O
2

Karena telah dikatakan bahwa ini tidak dapat dilakukan dengan cut, saya akan menunjukkan bahwa adalah mudah untuk menghasilkan solusi yang setidaknya tidak lebih buruk daripada yang lain, meskipun saya tidak menganjurkan penggunaan cutsebagai "yang terbaik" (atau bahkan solusi yang sangat baik). Harus dikatakan bahwa solusi apa pun yang tidak mencari secara khusus untuk *[dan di ]*sekitar digit membuat asumsi penyederhanaan dan karena itu rentan terhadap kegagalan pada contoh-contoh yang lebih kompleks daripada yang diberikan oleh penanya (misalnya angka di luar *[dan ]*, yang tidak boleh ditampilkan). Solusi ini memeriksa setidaknya untuk tanda kurung, dan dapat diperluas untuk memeriksa tanda bintang juga (dibiarkan sebagai latihan untuk pembaca):

cut -f 2 -d '[' myfile.txt | cut -f 1 -d ']'

Ini memanfaatkan -dopsi, yang menentukan pembatas. Jelas Anda juga bisa menyalurkan ke cutekspresi daripada membaca dari file. Walaupun cutmungkin cukup cepat, karena sederhana (tidak ada mesin regex), Anda harus memanggilnya setidaknya dua kali (atau beberapa waktu lagi untuk memeriksa *), yang menciptakan beberapa proses overhead. Satu keuntungan nyata dari solusi ini adalah lebih mudah dibaca, terutama untuk pengguna biasa yang tidak berpengalaman dalam konstruksi regex.

Thomas
sumber