Bagaimana memastikan bahwa string yang diinterpolasi ke dalam substitusi `sed` lolos dari semua metachar

21

Saya memiliki skrip yang membaca aliran teks dan menghasilkan file perintah sed yang kemudian dijalankan sed -f. Perintah sed yang dihasilkan seperti:

s/cid:image002\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1922/g
s/cid:image003\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1923/g
s/cid:image004\.jpg@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1924/g

Asumsikan skrip yang menghasilkan sedperintah adalah sesuatu seperti:

while read cid fileid
do
    cidpat="$(echo $cid | sed -e s/\\./\\\\./g)"
    echo 's/'"$cidpat"'/https:\/\/mysite.com\/files\/'"$fileid"'/g' >> sedscr
done

Bagaimana saya bisa memperbaiki skrip untuk memastikan semua karakter meteks regex dalam cidstring lolos dan diinterpolasi dengan benar?

dan
sumber

Jawaban:

24

Untuk menghindari variabel yang akan digunakan di sisi kiri dan kanan dari sperintah di sed( di sini $lhsdan $rhsmasing - masing), Anda harus melakukan:

escaped_lhs=$(printf '%s\n' "$lhs" | sed 's:[][\/.^$*]:\\&:g')
escaped_rhs=$(printf '%s\n' "$rhs" | sed 's:[\/&]:\\&:g;$!s/$/\\/')

sed "s/$escaped_lhs/$escaped_rhs/"

Catatan yang $lhstidak dapat berisi karakter baris baru.

Yaitu, pada LHS, lepas dari semua operator regexp ( ][.^$*), karakter yang melarikan diri itu sendiri ( \), dan pemisah ( /).

Pada RHS, Anda hanya perlu melarikan diri &, pemisah, garis miring terbalik dan karakter baris baru (yang Anda lakukan dengan memasukkan garis miring terbalik di akhir setiap baris kecuali yang terakhir ( $!s/$/\\/)).

Itu mengasumsikan Anda menggunakan /sebagai pemisah dalam sed sperintah Anda dan bahwa Anda tidak mengaktifkan Extended REs dengan -r(GNU sed/ ssed/ ast/ busybox sed) atau -E(BSDs,, astGNU baru-baru ini, busybox terbaru) atau PCREs dengan -R( ssed) atau Augmented REs dengan -A/ -X( ast) yang semua memiliki operator RE ekstra.

Beberapa aturan dasar saat berurusan dengan data sewenang-wenang:

  • Jangan gunakan echo
  • kutip variabel Anda
  • pertimbangkan dampak dari locale (terutama set karakternya: penting bahwa perintah escaping sed dijalankan di locale yang sama dengan sedperintah yang menggunakan string escaped (dan dengan sedperintah yang sama ) misalnya)
  • jangan lupa tentang karakter baris baru (di sini Anda mungkin ingin memeriksa apakah $lhsberisi dan mengambil tindakan).

Pilihan lain adalah menggunakan perlalih-alih seddan meneruskan string di lingkungan dan menggunakan operator \Q/ \E perlregexp untuk mengambil string secara harfiah:

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'

perl(secara default) tidak akan terpengaruh oleh set karakter lokal karena, di atas, ia hanya menganggap string sebagai array byte tanpa peduli tentang karakter apa (jika ada) yang mungkin mereka wakili untuk pengguna. Dengan sed, Anda dapat mencapai hal yang sama dengan memperbaiki lokal ke Cdengan LC_ALL=Cuntuk semua sedperintah (meskipun itu juga akan mempengaruhi bahasa pesan kesalahan, jika ada).

Stéphane Chazelas
sumber
Bagaimana jika saya harus menghindari tanda kutip ganda?
Menon
@Menon, tanda kutip ganda tidak spesial sed, Anda tidak perlu menghindarinya.
Stéphane Chazelas
Ini tidak dapat digunakan untuk pencocokan pola menggunakan wildcard, bukan?
Menon
@Menon, tidak, kecocokan pola wildcard seperti dengan find's -nameberbeda dari ekspresi reguler. Di sana Anda hanya perlu melarikan diri ?, *backslash dan[
Stéphane Chazelas