Bagaimana cara menangkap kelompok n digit, tetapi tidak lebih dari n?

33

Saya sedang belajar Linux, dan saya punya tantangan yang sepertinya tidak bisa saya selesaikan sendiri. Ini dia:

grep baris dari file yang berisi 4 angka berturut-turut tetapi tidak lebih dari 4.

Saya tidak yakin bagaimana mendekati ini. Saya dapat mencari angka-angka tertentu tetapi bukan jumlahnya dalam suatu string.

Budha
sumber
2
Haruskah garis suka 1234a12345ditampilkan, atau tidak?
Eliah Kagan
@Buddha Anda perlu menjelaskan pertanyaan Anda bersama dengan sebuah contoh.
Avinash Raj
jika angka didahului dengan spasi atau awal jangkar baris dan diikuti oleh spasi atau akhir jangkar baris maka Anda bisa menggunakan batas kata. \b\d{4}\b
Avinash Raj
1
Pertanyaan ini berbeda dari beberapa pertanyaan tentang ekspresi reguler dengan secara eksplisit tentang penggunaan grep . Pertanyaan tentang penggunaan utilitas Unix di Ubuntu, seperti grep, sed, dan awk, selalu dianggap baik-baik saja di sini. Terkadang orang bertanya bagaimana melakukan pekerjaan dengan alat yang salah ; maka kurangnya konteks adalah masalah besar, tetapi bukan itu yang terjadi di sini. Ini sesuai topik, cukup jelas untuk dijawab dengan bermanfaat, membantu komunitas kami, dan tidak ada manfaatnya mencegah jawaban lebih lanjut atau mendorongnya ke arah penghapusan atau migrasi. Saya memberikan suara untuk membukanya kembali.
Eliah Kagan
1
Terima kasih banyak, saya tidak tahu saya akan mendapatkan banyak umpan balik ini. Ini adalah jawaban yang saya cari: file grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])'. Perintah tersebut harus dapat menarik string seperti ini (yang berfungsi): abc1234abcd99999
Buddha

Jawaban:

52

Ada dua cara untuk menafsirkan pertanyaan ini; Saya akan membahas kedua kasus ini. Anda mungkin ingin menampilkan garis:

  1. yang berisi urutan empat digit yang dengan sendirinya bukan bagian dari urutan digit yang lebih panjang, atau
  2. yang berisi urutan empat digit tetapi tidak lagi urutan angka (bahkan tidak terpisah).

Misalnya, (1) akan ditampilkan 1234a56789, tetapi (2) tidak.


Jika Anda ingin menampilkan semua baris yang berisi urutan empat digit yang dengan sendirinya bukan bagian dari urutan digit yang lebih lama, salah satu caranya adalah:

grep -P '(?<!\d)\d{4}(?!\d)' file

Ini menggunakan ekspresi reguler Perl , yang didukung oleh Ubuntu grep( GNU grep ) -P. Itu tidak akan cocok dengan teks suka 12345, juga tidak akan cocok dengan 1234atau 2345yang merupakan bagian dari itu. Tapi itu akan cocok dengan 1234in 1234a56789.

Dalam ekspresi reguler Perl:

  • \dberarti angka apa saja (ini adalah cara singkat untuk mengatakan [0-9]atau [[:digit:]]).
  • x{4}cocok x4 kali. ( { }Sintaks tidak khusus untuk ekspresi reguler Perl; itu dalam ekspresi reguler yang diperluas grep -Ejuga.) Begitu \d{4}juga dengan \d\d\d\d.
  • (?<!\d)adalah pernyataan pandangan ke belakang negatif lebar nol. Itu berarti "kecuali didahului oleh \d."
  • (?!\d)adalah pernyataan pandangan ke depan negatif lebar nol. Itu berarti "kecuali diikuti oleh \d."

(?<!\d) dan (?!\d) jangan mencocokkan teks di luar urutan empat digit; alih-alih, mereka akan (saat digunakan bersama-sama) mencegah urutan empat digit dari dirinya sendiri dicocokkan jika itu adalah bagian dari urutan angka yang lebih panjang.

Menggunakan hanya melihat-belakang atau hanya melihat-depan tidak cukup karena urutan empat digit paling kanan atau paling kiri masih akan cocok.

Salah satu manfaat menggunakan pernyataan lihat-belakang dan lihat-depan adalah bahwa pola Anda hanya cocok dengan urutan empat digit itu sendiri, dan bukan teks di sekitarnya. Ini bermanfaat saat menggunakan penyorotan warna (dengan --coloropsi).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Secara default di Ubuntu, setiap pengguna memiliki filealias grep='grep --color=auto' mereka . Jadi Anda mendapatkan penyorotan warna secara otomatis ketika Anda menjalankan perintah sederhana dimulai dengan (ini adalah saat alias diperluas) dan output standar adalah terminal (inilah yang memeriksa). Cocok biasanya disorot dalam warna merah (dekat dengan vermilion ), tetapi saya telah menunjukkannya dalam huruf miring dicetak tebal. Berikut screenshotnya:~.bashrcgrep--color=auto
Cuplikan layar memperlihatkan perintah grep, dengan 12345abc789d0123e4 sebagai output, dengan 0123 disorot dengan warna merah.

Dan Anda bahkan dapat membuat grepcetak hanya teks yang cocok, dan bukan seluruh baris, dengan -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Cara Alternatif, Tanpa Tegas dan Tegas

Namun, jika Anda:

  1. memerlukan perintah yang juga akan berjalan pada sistem yang greptidak mendukung -Patau tidak ingin menggunakan ekspresi reguler Perl, dan
  2. tidak perlu mencocokkan empat digit secara khusus - yang biasanya terjadi jika tujuan Anda hanya untuk menampilkan garis yang berisi kecocokan, dan
  3. tidak apa-apa dengan solusi yang sedikit kurang elegan

... maka Anda dapat mencapai ini dengan ekspresi reguler yang diperluas sebagai gantinya:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Ini cocok dengan empat digit dan karakter non-digit - atau awal atau akhir garis - yang mengelilinginya. Secara khusus:

  • [0-9]cocok dengan digit mana pun (seperti [[:digit:]], atau \ddalam ekspresi reguler Perl) dan {4}berarti "empat kali." Jadi [0-9]{4}cocok dengan urutan empat digit.
  • [^0-9]cocok dengan karakter yang tidak berada dalam kisaran 0melalui 9. Ini sama dengan [^[:digit:]](atau \D, dalam ekspresi reguler Perl).
  • ^, ketika itu tidak muncul dalam [ ]tanda kurung, cocok dengan awal baris. Demikian pula, $cocok dengan akhir garis.
  • |berarti atau dan tanda kurung untuk pengelompokan (seperti dalam aljabar). Jadi (^|[^0-9])cocok dengan awal baris atau karakter non-digit, sementara ($|[^0-9])cocok dengan akhir baris atau karakter non-digit.

Jadi kecocokan hanya terjadi pada garis yang berisi urutan empat digit ( [0-9]{4}) yang secara bersamaan:

  • di awal baris atau didahului oleh non-digit ( (^|[^0-9])), dan
  • di akhir baris atau diikuti oleh non-digit ( ($|[^0-9])).

Jika, di sisi lain, Anda ingin menampilkan semua baris yang mengandung urutan empat digit, tetapi tidak mengandung salah urutan lebih dari empat digit (bahkan salah satu yang terpisah dari urutan lain dari hanya empat digit), maka secara konseptual Anda tujuannya adalah menemukan garis yang cocok dengan satu pola tetapi tidak yang lain.

Oleh karena itu, bahkan jika Anda tahu bagaimana melakukannya dengan pola tunggal, saya sarankan menggunakan sesuatu seperti saran kedua matt ,grep untuk dua pola secara terpisah.

Anda tidak mendapatkan banyak manfaat dari fitur lanjutan dari ekspresi reguler Perl saat melakukan itu, jadi Anda mungkin memilih untuk tidak menggunakannya. Namun sesuai dengan gaya di atas, berikut adalah pemendekan dari solusi matt menggunakan \d(dan kawat gigi) sebagai pengganti [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Sejak digunakan [0-9], cara matt lebih portabel - ini akan bekerja pada sistem yang greptidak mendukung ekspresi reguler Perl. Jika Anda menggunakan [0-9](atau [[:digit:]]) alih-alih \d, tetapi terus menggunakan { }, Anda mendapatkan portabilitas cara matt sedikit lebih ringkas:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

Cara Alternatif, Dengan Pola Tunggal

Jika Anda benar-benar lebih suka grepperintah itu

  1. menggunakan ekspresi reguler tunggal (bukan dua greps dipisahkan oleh pipa , seperti di atas)
  2. untuk menampilkan garis yang mengandung setidaknya satu urutan empat digit,
  3. tetapi tidak ada urutan lima (atau lebih) digit,
  4. dan Anda tidak keberatan mencocokkan seluruh baris, bukan hanya digit (Anda mungkin tidak keberatan ini)

... maka Anda dapat menggunakan:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

The -xmerek bendera grephanya menampilkan garis-garis di mana seluruh pertandingan line (bukan setiap baris mengandung pertandingan).

Saya telah menggunakan ekspresi reguler Perl karena saya pikir singkatnya \ddan \Dsecara substansial meningkatkan kejelasan dalam kasus ini. Tetapi jika Anda membutuhkan sesuatu yang portabel untuk sistem yang greptidak mendukung -P, Anda dapat menggantinya dengan [0-9]dan [^0-9](atau dengan [[:digit:]]dan [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

Cara kerja ekspresi reguler ini adalah:

  • Di tengah, \d{4}atau [0-9]{4}cocok dengan satu urutan empat digit. Kita mungkin memiliki lebih dari satu, tetapi kita harus memiliki setidaknya satu.

  • Di sebelah kiri, (\d{0,4}\D)*atau ([0-9]{0,4}[^0-9])*cocok dengan nol atau lebih ( *) contoh tidak lebih dari empat digit diikuti oleh non-digit. Nol digit (yaitu, tidak ada) adalah satu kemungkinan untuk "tidak lebih dari empat digit." Ini cocok dengan (a) string kosong atau (b) string yang diakhiri dengan non-digit dan tidak mengandung urutan lebih dari empat digit.

    Karena teks tepat di sebelah kiri tengah \d{4}(atau [0-9]{4}) harus kosong atau diakhiri dengan non-digit, ini mencegah pusat \d{4}dari mencocokkan empat digit yang memiliki digit (kelima) lainnya tepat di sebelah kiri mereka.

  • Di sebelah kanan, (\D\d{0,4})*atau ([^0-9][0-9]{0,4})*cocok dengan nol atau lebih ( *) contoh non-digit diikuti oleh tidak lebih dari empat digit (yang, seperti sebelumnya, bisa empat, tiga, dua, satu, atau bahkan tidak sama sekali). Ini cocok dengan (a) string kosong atau (b) string yang dimulai dengan non-digit dan tidak mengandung urutan lebih dari empat digit.

    Karena teks segera di sebelah kanan pusat \d{4}(atau [0-9]{4}) harus kosong atau mulai dengan non-digit, ini mencegah pusat \d{4}dari mencocokkan empat digit yang memiliki digit (kelima) lainnya tepat di sebelah kanannya.

Ini memastikan empat digit urutan hadir di suatu tempat, dan tidak ada urutan lima digit atau lebih yang hadir di mana saja.

Tidak buruk atau salah melakukannya dengan cara ini. Tetapi mungkin alasan paling penting untuk mempertimbangkan alternatif ini adalah bahwa ia mengklarifikasi manfaat menggunakan (atau serupa) sebagai gantinya, seperti yang disarankan di atas dan dalam jawaban matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

Dengan cara itu, jelas tujuan Anda adalah memilih garis yang berisi satu hal tetapi bukan yang lain. Plus sintaksinya lebih sederhana (sehingga mungkin lebih cepat dipahami oleh banyak pembaca / pengelola).

Eliah Kagan
sumber
9

Ini akan menunjukkan kepada Anda 4 angka berturut-turut tetapi tidak lebih

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Perhatikan ^ artinya tidak

Ada masalah dengan ini meskipun saya tidak yakin bagaimana cara memperbaikinya ... jika angkanya adalah akhir dari baris maka tidak akan muncul.

Namun versi yang lebih buruk ini akan bekerja untuk kasus itu

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
matt
sumber
oops, tidak perlu egrep - saya sudah mengeditnya
matt
2
Yang pertama salah - ditemukan a12345b, karena cocok 2345b.
Volker Siegel
0

Jika greptidak mendukung perl regular expressions ( -P), gunakan perintah shell berikut:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

dimana printf '[0-9]%.0s' {1..4}akan menghasilkan 4 kali [0-9]. Metode ini berguna, ketika Anda memiliki angka yang panjang dan Anda tidak ingin mengulangi polanya (cukup ganti 4dengan jumlah digit Anda untuk mencari).

Menggunakan -wakan mencari seluruh kata. Namun jika Anda tertarik pada string alfanumerik, seperti 1234a, lalu tambahkan [^0-9]di akhir pola, mis

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Menggunakan $()pada dasarnya adalah substitusi perintah . Periksa pos ini untuk melihat bagaimana printfpengulangan pola.

kenorb
sumber
0

Anda dapat mencoba perintah di bawah ini dengan mengganti filedengan nama file aktual di sistem Anda:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Anda juga dapat memeriksa tutorial ini untuk lebih banyak menggunakan perintah grep.

Mike Tyson
sumber