Karakter apa yang saya perlukan untuk melarikan diri saat menggunakan sed dalam skrip sh?

248

Ambil skrip berikut:

#!/bin/sh
sed 's/(127\.0\.1\.1)\s/\1/' [some file]

Jika saya mencoba menjalankan ini di sh( di dashsini), itu akan gagal karena tanda kurung, yang perlu melarikan diri. Tetapi saya tidak perlu melarikan diri dari garis miring terbalik sendiri (antara oktet, atau dalam \satau \1). Apa aturannya di sini? Bagaimana dengan kapan saya harus menggunakan {...}atau [...]? Apakah ada daftar apa yang saya lakukan dan tidak perlu melarikan diri?

detly
sumber
1
Berikut ini adalah fungsi bash untuk mengubah jalur untuk digunakan dengan SED:function sedPath { path=$((echo $1|sed -r 's/([\$\.\*\/\[\\^])/\\\1/g'|sed 's/[]]/\[]]/g')>&1) } #Escape path for use with sed
user2428118
Dura lex, sed sed
Nemo

Jawaban:

282

Ada dua level interpretasi di sini: shell, dan sed.

Dalam shell, segala sesuatu di antara tanda kutip tunggal ditafsirkan secara harfiah, kecuali tanda kutip tunggal sendiri. Anda dapat secara efektif memiliki kutipan tunggal antara kutipan tunggal dengan menulis '\''(tutup kutipan tunggal, satu kutipan tunggal literal, kutipan tunggal terbuka).

Sed menggunakan ekspresi reguler dasar . Dalam BRE, agar mereka diperlakukan secara harfiah, karakter $.*[\^harus dikutip dengan mendahului mereka dengan garis miring terbalik, kecuali di dalam set karakter ( […]). Surat, angka, dan (){}+?|tidak boleh dikutip (Anda bisa lolos dengan mengutip beberapa dari ini dalam beberapa implementasi). Urutan \(, \), \n, dan dalam beberapa implementasi \{, \}, \+, \?, \|dan backslash lainnya + alphanumerics memiliki arti khusus. Anda bisa lolos dengan tidak mengutip $^di beberapa posisi di beberapa implementasi.

Selain itu, Anda perlu garis miring terbalik sebelumnya /jika ingin ditampilkan di regex di luar ekspresi braket. Anda dapat memilih karakter alternatif sebagai pembatas dengan menulis, misalnya, s~/dir~/replacement~atau \~/dir~p; Anda akan memerlukan garis miring terbalik sebelum pembatas jika Anda ingin memasukkannya ke dalam BRE. Jika Anda memilih karakter yang memiliki arti khusus dalam BRE dan Anda ingin memasukkannya secara harfiah, Anda akan membutuhkan tiga garis miring terbalik; Saya tidak merekomendasikan ini, karena mungkin berperilaku berbeda di beberapa implementasi.

Singkatnya, untuk sed 's/…/…/':

  • Tuliskan regex di antara tanda kutip tunggal.
  • Gunakan '\''untuk mengakhiri dengan satu kutipan di regex.
  • Letakkan garis miring terbalik sebelum $.*/[\]^dan hanya karakter tersebut (tetapi tidak di dalam ekspresi braket). (Secara teknis Anda tidak harus melakukan backslash sebelumnya, ]tetapi saya tidak tahu implementasi yang memperlakukan ]dan \]berbeda di luar ekspresi braket.)
  • Di dalam ungkapan kurung, untuk -diperlakukan secara harfiah, pastikan itu pertama atau terakhir ( [abc-]atau [-abc], tidak [a-bc]).
  • Di dalam ekspresi braket, untuk ^diperlakukan secara harfiah, pastikan itu bukan yang pertama (gunakan [abc^], bukan [^abc]).
  • Untuk memasukkan ]dalam daftar karakter yang cocok dengan ekspresi braket, jadikan itu karakter pertama (atau setelah pertama ^untuk set yang dinegasikan): []abc]atau [^]abc](tidak [abc]]juga[abc\]] ).

Dalam teks pengganti:

  • &dan \perlu dikutip dengan mendahului mereka dengan garis miring terbalik, seperti halnya pembatas (biasanya /) dan baris baru.
  • \diikuti oleh angka memiliki arti khusus. \diikuti oleh huruf memiliki arti khusus (karakter khusus) dalam beberapa implementasi, dan \diikuti oleh beberapa karakter lain berarti \catau ctergantung pada implementasinya.
  • Dengan tanda kutip tunggal di sekitar argumen ( sed 's/…/…/'), gunakan '\''untuk menempatkan tanda kutip tunggal dalam teks pengganti.

Jika regex atau teks pengganti berasal dari variabel shell, ingat itu

  • Regex adalah BRE, bukan string literal.
  • Di regex, baris baru harus dinyatakan sebagai \n(yang tidak akan pernah cocok kecuali Anda memiliki sedkode lain menambahkan karakter baris baru ke ruang pola). Tetapi perhatikan bahwa itu tidak akan bekerja di dalam ekspresi braket dengan beberapa sedimplementasi.
  • Dalam teks pengganti &,, \dan baris baru perlu dikutip.
  • Pembatas perlu dikutip (tetapi tidak dalam ekspresi braket).
  • Gunakan tanda kutip ganda untuk interpolasi: sed -e "s/$BRE/$REPL/".
Gilles
sumber
Melarikan karakter wildcard yang sebenarnya (*) Anda dapat menggunakan double backslash ( \\*). Contoh:echo "***NEW***" | sed /\\*\\*\\*NEW\\*\\*\\*/s/^/#/
hazard89
43

Masalah yang Anda alami bukan karena interpolasi shell dan lolos - itu karena Anda mencoba menggunakan sintaks ekspresi reguler yang diperluas tanpa melewati opsi -ratau --regexp-extendedopsi.

Ubah sed line Anda dari

sed 's/(127\.0\.1\.1)\s/\1/' [some file]

untuk

sed -r 's/(127\.0\.1\.1)\s/\1/' [some file]

dan itu akan berhasil karena saya yakin Anda berniat.

Secara default, penggunaan menggunakan ekspresi reguler dasar (gaya think grep), yang akan membutuhkan sintaks berikut:

sed 's/\(127\.0\.1\.1\)[ \t]/\1/' [some file]
R Perrin
sumber
Saya memiliki masalah ini lagi, dan lupa untuk menggulir ke bawah untuk menemukan solusi yang saya unduh terakhir kali. Terima kasih lagi.
isaaclw
Terima kasih banyak. Menambahkan -rsebagai opsi adalah apa yang diperlukan dalam kasus saya.
HelloGoodbye
15

Kecuali jika Anda ingin menginterpolasi variabel shell ke ekspresi sed, gunakan tanda kutip tunggal untuk seluruh ekspresi karena mereka menyebabkan segala sesuatu di antara mereka ditafsirkan apa adanya, termasuk backslash.

Jadi jika Anda ingin sed melihat s/\(127\.0\.1\.1\)\s/\1/tanda kutip tunggal di sekitarnya dan shell tidak akan menyentuh tanda kurung atau garis miring terbalik di dalamnya. Jika Anda perlu menginterpolasi variabel shell, masukkan hanya bagian itu dalam tanda kutip ganda. Misalnya

sed 's/\(127\.0\.1\.1\)/'"$ip"'/'

Ini akan menyelamatkan Anda dari kesulitan mengingat karakter meta shell yang tidak lolos oleh tanda kutip ganda.

Kyle Jones
sumber
Saya ingin sedmelihat s/(127\.0\.1\.1)/..., tetapi menempatkan itu dalam skrip shell apa adanya tidak bekerja. Apa yang Anda katakan tentang cangkang yang tidak menyentuh tanda kurung tampaknya salah. Saya telah mengedit pertanyaan saya untuk menguraikan.
detly
3
Shell tidak menyentuh tanda kurung. Anda perlu backslases karena sed perlu melihatnya. sed 's/(127\.0\.1\.1)/IP \1/'gagal karena sed perlu melihat \(dan \)untuk sintaksis grup, bukan (dan ).
Kyle Jones
facepalm Ini tidak ada di halaman manual, tetapi ada di beberapa manual online yang saya temukan. Apakah ini normal untuk regex, karena saya belum pernah menggunakannya di perpustakaan regex (dalam, misalnya. Python)?
detly
3
Untuk perintah Unix tradisional, ada ekspresi reguler dasar dan ekspresi reguler lanjutan. Detail . sed menggunakan ekspresi reguler dasar, sehingga garis miring terbalik diperlukan untuk sintaksis grup. Perl dan Python bahkan melampaui ekspresi reguler yang diperluas. Ketika saya mencari-cari, saya menemukan grafik yang sangat informatif yang menggambarkan betapa membingungkan yang kita bayangkan ketika kita dengan jelas mengatakan "ekspresi reguler."
Kyle Jones
1
Saya juga akan menambahkan bahwa satu-satunya karakter yang tidak dapat digunakan di dalam kutipan tunggal adalah kutipan tunggal.
enzotib