Grep: Tanda bintang (*) tidak selalu berfungsi

11

Jika saya menerima dokumen yang berisi berikut ini:

ThisExampleString

... untuk ekspresi This*Stringatau *String, tidak ada yang dikembalikan. Namun, This*mengembalikan garis di atas seperti yang diharapkan.

Apakah ungkapan itu terlampir dalam tanda kutip tidak ada bedanya.

Saya pikir tanda bintang menunjukkan sejumlah karakter yang tidak dikenal? Mengapa ini hanya berfungsi jika itu pada awal ekspresi? Jika ini perilaku yang dimaksudkan, apa yang harus saya gunakan daripada ekspresi This*Stringdan *String?

Trae
sumber
karena itu bukan cara kerja regex ... (khususnya: * != any number of unknown characters
.membaca

Jawaban:

18

Tanda bintang dalam ekspresi reguler berarti "cocok dengan elemen sebelumnya 0 kali atau lebih".

Dalam kasus khusus Anda dengan grep 'This*String' file.txt, Anda mencoba untuk mengatakan, "hei, grep, cocokkan dengan saya kata Thi, diikuti dengan huruf kecil snol atau lebih kali, diikuti oleh kata String". Huruf kecil stidak ditemukan Example, karenanya grep mengabaikan ThisExampleString.

Dalam kasus grep '*String' file.txt, Anda mengatakan "grep, cocokkan saya dengan string kosong - secara harfiah tidak ada - sebelum kata String". Tentu saja, bukan ThisExampleStringitu yang seharusnya dibaca. (Ada arti lain yang mungkin - Anda dapat mencoba ini dengan dan tanpa -Ebendera - tetapi tidak ada artinya yang seperti yang Anda inginkan di sini.)

Mengetahui bahwa .berarti "setiap karakter tunggal", kita bisa melakukan ini: grep 'This.*String' file.txt. Sekarang perintah grep akan membacanya dengan benar: Thisdiikuti oleh karakter apa pun (anggap saja sebagai pemilihan karakter ASCII) diulang beberapa kali, diikuti oleh String.

Sergiy Kolodyazhnyy
sumber
6
Dalam Bash (dan sebagian besar kerang Unix) *adalah karakter khusus dan harus dikutip atau melarikan diri misalnya seperti ini: grep 'This*String' file.txtatau ini: grep This\*String file.txtuntuk tidak terkejut dengan hasil yang tidak terduga.
pabouk
2
@ Sabouk di kerang, *adalah wildcard. Dalam grep, *adalah operator ekspresi reguler. Lihat unix.stackexchange.com/q/57957/70524
muru
11
pabouk benar, ekspansi nama file terjadi sebelum perintah dijalankan; bandingkan strace grep .* file.txt |& head -n 1 dan strace grep '.*' file.txt |& head -n 1. Juga benar-benar grepberfungsi juga dengan karakter Unicode (misalnya echo -ne ⇏ | grep ⇏output )
kos
1
@Serg: Anda memiliki reputasi tinggi di sini jadi saya pikir Anda segera memperhatikan apa yang saya maksud. OP telah menandai bash pertanyaan jadi saya menganggap perintah yang dibahas diinterpretasikan oleh bash. Ini berarti bahwa pertama bashmenginterpretasikan karakter khusus dan hanya setelah semua dilakukan ekspansi itu melewati parameter ke proses spawned. ----- Misalnya perintah ini di Bash: grep This.\*String file.txtakan bertelur /bin/grepdengan parameter ini 0: grep, 1: This.*String, 2: file.txt. Perhatikan bahwa Bash menghapus backslash dan yang semula lolos *disahkan secara harfiah.
pabouk
7
Yang lucu (dan untuk pemecahan masalah :) cukup jahat adalah bahwa perintah Anda seperti grep This.*String file.txtbiasanya akan bekerja karena kemungkinan besar tidak akan ada file yang cocok dengan ekspresi wildcard shell This.*String. Dalam kasus seperti itu secara default Bash akan melewati argumen termasuk secara harfiah *.
pabouk
8

The *metakarakter di BRE 1 s, ERE 1 s dan PCRE 1 s pertandingan 0 atau lebih kejadian dari pola sebelumnya dikelompokkan (jika pola dikelompokkan adalah mendahului *metakarakter), 0 atau lebih kejadian dari kelas karakter sebelumnya (jika kelas karakter adalah mendahului *metacharacter) atau 0 atau lebih kejadian dari karakter sebelumnya (jika tidak ada pola yang dikelompokkan atau kelas karakter sebelum *metacharacter);

Ini berarti bahwa dalam This*Stringpola, sebagai *metacharacter tidak didahului oleh pola yang dikelompokkan atau kelas karakter, *metacharacter cocok dengan 0 atau lebih kejadian dari karakter sebelumnya (dalam hal ini skarakter):

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Untuk mencocokkan 0 atau lebih kejadian karakter apa pun, Anda ingin mencocokkan 0 atau lebih kejadian .metacharacter, yang cocok dengan karakter apa pun:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

The *metakarakter di Bres dan Eres selalu "serakah", yaitu akan cocok pertandingan terpanjang:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Ini mungkin bukan perilaku yang diinginkan; jika tidak, Anda dapat menghidupkan grepmesin PCRE (menggunakan -Popsi) dan menambahkan ?metacharacter, yang ketika diletakkan setelah *dan +metachar karakter memiliki efek mengubah keserakahan mereka:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: Ekspresi Reguler Dasar, Ekspresi Reguler Diperpanjang dan Ekspresi Reguler Kompatibel yang Kompatibel

kos
sumber
Terima kasih atas jawaban yang sangat informatif. Namun, saya memilih jawaban yang berbeda karena lebih pendek dan lebih mudah dipahami. +1 untuk memberikan begitu banyak detail.
Trae
@Trae Sama-sama. Tidak apa-apa, saya setuju bahwa mungkin ini terlalu rumit dan membuat terlalu banyak asumsi untuk seseorang yang tidak terlalu akrab dengan topik tersebut.
kos
4

Salah satu penjelasan yang ditemukan di sini tautan :

Tanda bintang " *" tidak berarti hal yang sama dalam ekspresi reguler seperti pada wildcarding; itu adalah pengubah yang berlaku untuk karakter tunggal sebelumnya, atau ekspresi seperti [0-9]. Tanda bintang cocok dengan nol atau lebih dari yang sebelumnya. Maka [A-Z]*cocok dengan sejumlah huruf besar, termasuk tidak ada, sementara [A-Z][A-Z]*cocok dengan satu atau lebih huruf besar.

Ova
sumber
1

*memiliki makna khusus baik sebagai karakter shell globbing ("wildcard") dan sebagai metacharacter ekspresi reguler . Anda harus memperhitungkan keduanya, meskipun jika Anda mengutip ekspresi reguler Anda, maka Anda dapat mencegah shell memperlakukannya secara khusus dan memastikannya tidak berubah grep. Meskipun agak mirip secara konseptual, apa *artinya shell sangat berbeda dari apa artinya grep.

Pertama , shell memperlakukan *sebagai wildcard.

Kamu berkata:

Apakah ungkapan itu terlampir dalam tanda kutip tidak ada bedanya.

Itu tergantung pada file apa yang ada di direktori apa pun Anda berada ketika Anda menjalankan perintah. Untuk pola yang mengandung pemisah direktori /, itu mungkin tergantung pada file apa yang ada di seluruh sistem Anda. Anda harus selalu mengutip ekspresi reguler untuk grep- dan tanda kutip tunggal biasanya yang terbaik - kecuali jika Anda yakin Anda baik-baik saja dengan sembilan jenis transformasi yang berpotensi mengejutkan yang dilakukan shell sebelum menjalankan grepperintah.

Ketika shell menemukan *karakter yang tidak dikutip , itu berarti "nol atau lebih dari karakter apa pun" dan menggantikan kata yang mengandungnya dengan daftar nama file yang cocok dengan pola. (Nama file yang diawali dengan .dikecualikan - kecuali pola Anda sendiri dimulai dengan . atau Anda sudah mengkonfigurasi shell Anda untuk memasukkannya.) Ini dikenal sebagai globbing - dan juga dengan nama ekspansi nama file dan ekspansi nama path .

Efek dengan grepbiasanya adalah bahwa nama file yang cocok pertama diambil sebagai ekspresi reguler - bahkan jika itu akan sangat jelas bagi pembaca manusia bahwa itu tidak dimaksudkan sebagai ekspresi reguler - sementara semua nama file lain terdaftar secara otomatis dari Anda Glob diambil sebagai file di dalamnya untuk mencari kecocokan. (Anda tidak melihat daftar - itu diteruskan dengan tidak jelas ke grep.) Anda hampir tidak pernah ingin ini terjadi.

Alasan mengapa hal ini terkadang bukan masalah - dan dalam kasus khusus Anda, setidaknya sejauh ini tidak - adalah yang *akan dibiarkan jika semua hal berikut ini benar :

  1. Tidak ada file yang namanya cocok. ... Atau Anda telah menonaktifkan globbing di shell Anda, biasanya dengan set -fatau yang setara set -o noglob. Tapi ini tidak biasa dan Anda mungkin tahu Anda melakukannya.

  2. Anda menggunakan shell yang perilaku defaultnya dibiarkan begitu *saja ketika tidak ada nama file yang cocok. Ini adalah kasus di Bash, yang mungkin Anda gunakan, tetapi tidak di semua kerang Bourne-style. (Perilaku default di shell populer Zsh, misalnya, adalah untuk gumpalan untuk (a) memperluas atau (b) menghasilkan kesalahan.) ... Atau Anda telah mengubah perilaku shell Anda - bagaimana yang dilakukan bervariasi lintas kerang.

  3. Anda belum dinyatakan kepada shell Anda untuk memungkinkan gumpalan untuk diganti dengan apa-apa jika tidak ada file yang cocok, atau gagal dengan pesan kesalahan dalam situasi ini. Di Bash yang akan dilakukan dengan mengaktifkan opsi nullglobatau failglob shell , masing-masing.

Anda terkadang dapat mengandalkan # 2 dan # 3 tetapi Anda jarang dapat mengandalkan # 1. Sebuah grepperintah dengan pola kuotasi yang bekerja sekarang mungkin berhenti bekerja ketika Anda memiliki file yang berbeda atau ketika Anda menjalankannya dari tempat yang berbeda. Mengutip ekspresi reguler Anda dan masalahnya hilang.

Kemudian pada grepperintah memperlakukan *sebagai quantifier a.

Jawaban lain - seperti yang oleh Sergiy Kolodyazhnyy dan oleh kos - juga mengatasi aspek pertanyaan ini, dengan cara yang agak berbeda. Jadi saya mendorong mereka yang belum membacanya untuk melakukannya, baik sebelum atau sesudah membaca sisa jawaban ini.

Dengan asumsi *memang membuatnya untuk grep - yang mengutip harus memastikan - grepkemudian berarti bahwa item yang mendahuluinya dapat terjadi beberapa kali , daripada harus terjadi tepat sekali . Itu masih bisa terjadi sekali. Atau mungkin tidak ada sama sekali. Atau bisa diulang. Teks yang cocok dengan segala kemungkinan itu akan dicocokkan.

Apa yang saya maksud dengan "item"?

  • Satu karakter . Sejak bpertandingan literal sebuah b, b*cocok dengan nol atau lebih bs, sehingga ab*ccocok ac, abc, abbc, abbbc, dll

    Demikian pula, karena .pertandingan karakter apapun , .*cocok dengan nol atau lebih karakter 1 , sehingga a.*cpertandingan ac, akc, ahjglhdfjkdlgjdfkshlgc, bahkan acccccchjckhcc, dll Atau

  • Kelas karakter . Sejak [xy]pertandingan xatau y, [xy]*pertandingan nol atau lebih karakter di mana masing-masing adalah baik xatau y, sehingga p[xy]*qcocok pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, dll

    Ini juga berlaku untuk singkatan bentuk dari kelas karakter seperti \w, \W, \s, dan \S. Karena \wcocok dengan karakter kata apa pun, \w*cocok dengan nol atau lebih karakter kata. Atau

  • Sebuah kelompok . Sejak \(bar\)pertandingan bar, \(bar\)*pertandingan nol atau lebih bars, sehingga foo\(bar\)*bazcocok foobaz, foobarbaz, foobarbarbaz, foobarbarbarbaz, dll

    Dengan opsi -Eatau -P, masing-masing grepmemperlakukan ekspresi reguler Anda sebagai ERE atau PCRE , bukan sebagai BRE , dan kemudian grup dikelilingi oleh ( )alih-alih \( \), jadi Anda akan menggunakan (bar)alih- alih \(bar\)dan foo(bar)bazsebagai ganti foo\(bar\)baz.

man grepmemberikan penjelasan yang cukup dapat diakses tentang sintaks BRE dan ERE di bagian akhir, serta daftar semua opsi baris perintah yang grepditerima di awal. Saya merekomendasikan halaman manual sebagai sumber daya, dan juga dokumentasi GNU Grep dan situs tutorial / referensi ini (yang saya tautkan ke sejumlah halaman, di atas).

Untuk pengujian dan pembelajaran grep, saya sarankan memanggilnya dengan pola tetapi tanpa nama file. Kemudian dibutuhkan input dari terminal Anda. Masukkan garis; baris yang digemakan kembali kepada Anda adalah orang-orang yang berisi teks yang cocok dengan pola Anda. Untuk keluar, tekan Ctrl+ Ddi awal baris, yang menandakan akhir input. (Atau Anda dapat menekan Ctrl+ Cseperti kebanyakan program baris perintah). Misalnya:

grep 'This.*String'

Jika Anda menggunakan --colorbendera, grepakan menyorot bagian- bagian spesifik dari garis Anda yang cocok dengan ekspresi reguler Anda, yang sangat berguna untuk menentukan apa yang dilakukan ekspresi reguler dan untuk menemukan apa yang Anda cari setelah Anda melakukannya. Secara default, pengguna Ubuntu memiliki alias Bash yang menyebabkan grep --color=automenjalankan - yang cukup untuk tujuan ini - ketika Anda menjalankan grepdari baris perintah, sehingga Anda bahkan mungkin tidak perlu untuk lulus --colorsecara manual.

1 Oleh karena itu, .*dalam ekspresi reguler berarti apa *artinya di dalam shell glob. Namun, perbedaannya adalah bahwa grepsecara otomatis mencetak garis yang berisi kecocokan Anda di mana saja di dalamnya, sehingga biasanya tidak perlu ada .*di awal atau akhir ekspresi reguler.

Eliah Kagan
sumber