Jumlah backslash yang dibutuhkan untuk lolos dari backslash regex pada command-line

12

Saya baru-baru ini mengalami masalah dengan beberapa regex pada command-line, dan menemukan bahwa untuk mencocokkan backslash, jumlah karakter yang berbeda dapat digunakan. Angka ini tergantung pada kutipan yang digunakan untuk regex (tidak ada, harga tunggal, harga ganda). Lihat sesi bash berikut untuk maksud saya:

echo "#ab\\cd" > file
grep -E ab\cd file
grep -E ab\\cd file
grep -E ab\\\cd file
grep -E ab\\\\cd file
#ab\cd
grep -E ab\\\\\cd file
#ab\cd
grep -E ab\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\\cd file
grep -E "ab\cd" file
grep -E "ab\\cd" file
grep -E "ab\\\cd" file
#ab\cd
grep -E "ab\\\\cd" file
#ab\cd
grep -E "ab\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\\cd" file
grep -E 'ab\cd' file
grep -E 'ab\\cd' file
#ab\cd
grep -E 'ab\\\cd' file
#ab\cd
grep -E 'ab\\\\cd' file

Ini berarti:

  • tanpa tanda kutip, saya dapat mencocokkan backslash dengan 4-7 backslash yang sebenarnya
  • dengan tanda kutip ganda, saya dapat mencocokkan backslash dengan 3-6 backslash yang sebenarnya
  • Dengan tanda kutip tunggal, saya dapat mencocokkan backslash dengan 2-3 backslash yang sebenarnya

Saya mengerti bahwa satu backslash tambahan diabaikan oleh shell (dari halaman bash man):

"Backslash (\) yang tidak dikutip adalah karakter pelarian. Ini mempertahankan nilai literal dari karakter selanjutnya yang mengikuti"

Ini tidak berlaku untuk contoh yang dikutip tunggal, karena tidak ada pelarian yang dilakukan dalam kutipan tunggal.

Dan satu backslash tambahan diabaikan oleh perintah grep ("\ c" hanya "c" lolos, tetapi ini sama dengan "c", karena "c" tidak memiliki arti khusus dalam regex).

Ini menjelaskan perilaku contoh dengan tanda kutip tunggal, tapi saya tidak benar-benar memahami dua contoh lainnya, terutama mengapa ada perbedaan antara string tidak-dikutip dan string yang dikutip ganda.

Sekali lagi, kutipan dari halaman bash man:

"Menutup karakter dalam tanda kutip ganda mempertahankan nilai literal semua karakter dalam tanda kutip, dengan pengecualian $,`, \, dan, ketika ekspansi sejarah diaktifkan,! "

Saya mencoba hal yang sama dengan GNU awk (eg awk /ab\cd/{print} file), dengan hasil yang sama.

Perl, bagaimanapun, menunjukkan hasil yang berbeda (menggunakan misalnya perl -ne "/ab\\cd/"\&\&print file):

  • tanpa tanda kutip, saya dapat mencocokkan backslash dengan 4-5 backslash yang sebenarnya
  • dengan tanda kutip ganda, saya dapat mencocokkan backslash dengan 3-4 backslash yang sebenarnya
  • Dengan tanda kutip tunggal, saya dapat mencocokkan backslash dengan 2 backslash yang sebenarnya

Adakah yang bisa menjelaskan perbedaan antara string regex yang tidak dikutip dan double-qouted pada command-line untuk grep dan awk? Saya tidak begitu tertarik pada penjelasan tentang perilaku Perl, karena saya biasanya tidak menggunakan Perl satu kalimat.

daniel kullmann
sumber

Jawaban:

10

Untuk contoh yang tidak dikutip, masing-masing \\pasangan melewati satu garis miring terbalik untuk grep, jadi 4 garis miring terbalik melewati garis dua untuk bergerak, yang diterjemahkan menjadi garis miring terbalik tunggal. 6 backslash meneruskan tiga ke grep, menerjemahkan menjadi satu backslash dan satu \c, yang sama dengan c. Satu backslash tambahan tidak mengubah apa pun, karena diterjemahkan \c- coleh shell. Delapan backslash dalam shell adalah empat dalam grep, diterjemahkan menjadi dua, jadi ini tidak cocok lagi.

Sebagai contoh dalam tanda kutip ganda, perhatikan apa yang mengikuti kutipan kedua Anda dari halaman bash:

Garis miring terbalik mempertahankan makna khusus hanya ketika diikuti oleh salah satu karakter berikut: $, `,", \, atau baris baru.

Yaitu ketika Anda memberikan jumlah garis miring terbalik yang aneh, urutannya berakhir \c, yang akan sama dengan cdalam tanda kutip, tetapi ketika dikutip, garis miring terbalik kehilangan makna khususnya, jadi \cditeruskan ke grep. Itulah mengapa kisaran backslash "mungkin" (yaitu pola yang cocok dengan file contoh Anda) meluncur turun satu per satu.

Ansgar Esztermann
sumber
... dan kemudian ada beberapa keanehan: sebagai contoh: printf "\ntest"akan menyisipkan baris baru sebelum "test", meskipun "\n"seharusnya telah diterjemahkan "n"oleh shell seperti halnya dua tanda kutip ganda ... (jadi hasil yang diharapkan seharusnya, untuk "\ ntest", "ntest". Kita harus memiliki kebiasaan untuk menulis: printf "\\ntest"atau printf '\ntest', tetapi entah bagaimana saya melihat banyak naskah yang mengandalkan keanehan sebagai gantinya.
Olivier Dulac
6

Tautan ini menjelaskan tentang bash Quotes and Escaping

Pertanyaan Anda berkaitan dengan tiga bagian pertama.

  • Pelarian per karakter
  • Lemah mengutip "tanda kutip ganda"
  • Kutipan kuat 'kutipan tunggal'
  • ANSI C menyukai kutipan string
  • Kutipan I18N / L10N (Internasionalisasi dan Pelokalan) .

Di bawah ini adalah bagan tentang bagaimana string yang bashditeruskan ke mereka grepdan seberapa grepjauh menafsirkannya secara internal.

Mari kita lihat dulu echo "#ab\\cd" > file.
Dalam tanda kutip yang lemah ("") "#ab\\cd", \\ini adalah pelarian \yang diteruskan filesebagai satu literal \. Jadi, fileberisi ab\cd

Sekarang, untuk perintah Anda: Grafik di bawah ini dapat membantu untuk melihat apa yang sebenarnya terjadi dengan setiap panggilan. The *menunjukkan orang-orang yang sesuai dengan isi file. Ini benar-benar hanya masalah menerapkan aturan melarikan diri bash, seperti pada halaman web, dengan catatan khusus untuk jawaban daniel kullmann di mana ia merujuk pada melarikan diri dari perilaku dalam situasi mengutip yang lemah .

Garis miring terbalik mempertahankan makna khusus hanya ketika diikuti oleh salah satu karakter berikut: $, `,", \, atau baris baru.


                            bash passes    grep further
                            to grep        resolves to         
grep -E ab\cd file            abcd           abcd   
grep -E ab\\cd file           ab\cd          abcd  
grep -E ab\\\cd file          ab\cd          abcd
grep -E ab\\\\cd file         ab\\cd         ab\cd    * 
grep -E ab\\\\\cd file        ab\\\cd        ab\cd    *
grep -E ab\\\\\\cd file       ab\\\cd        ab\cd    *    
grep -E ab\\\\\\\cd file      ab\\\cd        ab\cd    *
grep -E ab\\\\\\\\cd file     ab\\\\cd       ab\\cd

grep -E "ab\cd" file          ab\cd          abcd
grep -E "ab\\cd" file         ab\cd          abcd
grep -E "ab\\\cd" file        ab\\cd         ab\cd    *
grep -E "ab\\\\cd" file       ab\\cd         ab\cd    *
grep -E "ab\\\\\cd" file      ab\\\cd        ab\cd    *
grep -E "ab\\\\\\cd" file     ab\\\cd        ab\cd    *
grep -E "ab\\\\\\\cd" file    ab\\\\cd       ab\\cd    

grep -E 'ab\cd' file          ab\cd          abcd  
grep -E 'ab\\cd' file         ab\\cd         ab\cd    *
grep -E 'ab\\\cd' file        ab\\\cd        ab\cd    *
grep -E 'ab\\\\cd' file       ab\\\\cd       ab\\cd
Peter.O
sumber