Melarikan diri dari string untuk mengganti pola sed

317

Dalam skrip bash saya, saya memiliki string eksternal (diterima dari pengguna), yang harus saya gunakan dalam pola sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

Bagaimana saya bisa lepas dari $REPLACEstring sehingga akan diterima dengan aman sedsebagai pengganti literal?

CATATAN: The KEYWORDadalah substring bodoh dengan tidak ada pertandingan dll Hal ini tidak disediakan oleh pengguna.

Alexander Gladysh
sumber
13
Apakah Anda mencoba menghindari masalah "Tabel Kecil Bobby" jika mereka mengatakan "/ g -e / PASSWORD =. * / PASSWORD = abc / g '"?
Paul Tomblin
2
Jika menggunakan bash, Anda tidak perlu sed. Cukup gunakanoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson
@destenson Saya pikir Anda tidak harus meletakkan dua variabel di luar tanda kutip. Bash dapat membaca variabel di dalam tanda kutip ganda (dalam contoh Anda, spasi putih dapat mengacaukan segalanya).
Camilo Martin
2
Lihat juga: stackoverflow.com/q/29613304/45375
mklement0
1
@CamiloMartin, lihat komentar saya pada jawaban saya sendiri. Kutipan di dalam $ {} tidak cocok dengan tanda kutip di dalam. Kedua variabel tidak di luar tanda kutip.
destenson

Jawaban:

268

Peringatan : Ini tidak mempertimbangkan baris baru. Untuk jawaban yang lebih mendalam, lihat pertanyaan SO ini . (Terima kasih, Ed Morton & Niklas Peter)

Perhatikan bahwa melarikan diri dari segala sesuatu adalah ide yang buruk. Sed membutuhkan banyak karakter untuk melarikan diri untuk mendapatkan makna khusus mereka. Misalnya, jika Anda melepaskan satu digit dalam string pengganti, itu akan berubah menjadi referensi-ulang.

Seperti yang dikatakan Ben Blank, hanya ada tiga karakter yang perlu diloloskan dalam string pengganti (melarikan diri, meneruskan garis miring untuk akhir pernyataan dan & untuk mengganti semua):

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

Jika Anda perlu keluar dari KEYWORDstring, berikut ini yang Anda butuhkan:

sed -e 's/[]\/$*.^[]/\\&/g'

Dan bisa digunakan oleh:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

Ingat, jika Anda menggunakan karakter selain /sebagai pembatas, Anda perlu mengganti garis miring pada ekspresi di atas dengan karakter yang Anda gunakan. Lihat komentar PeterJCLaw untuk penjelasan.

Diedit: Karena beberapa kasus sudut yang sebelumnya tidak diperhitungkan, perintah di atas telah berubah beberapa kali. Periksa riwayat edit untuk detailnya.

Pianosaurus
sumber
17
Perlu dicatat bahwa Anda dapat menghindari melarikan diri dari garis miring ke depan dengan tidak menggunakannya sebagai pembatas. Sebagian besar (semua?) Versi sed memungkinkan Anda untuk menggunakan karakter apa pun, asalkan sesuai dengan pola: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw
2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' tidak bekerja untuk saya di OSX tetapi ini tidak: sed 's / ([\\ & /]] / \\ & / g 'dan ini sedikit lebih pendek.
jcoffland
1
Untuk pola pencarian KEYWORD, di sed GNU , berikut adalah 2 karakter lagi ^, $tidak disebutkan di atas:s/[]\/$*.^|[]/\\&/g
Peter.O
1
@Jesse: Tetap. Sebenarnya, itu adalah kesalahan yang saya peringatkan pada paragraf pertama. Saya kira saya tidak mempraktekkan apa yang saya khotbahkan.
Pianosaurus
1
@NeronLeVelu: Saya tidak yakin saya tahu apa yang Anda maksud, tetapi "tidak memiliki arti khusus dalam pipa atau variabel. Ini diurai oleh shell sebelum menjalankan hasilnya, jadi tanda kutip ganda di dalam variabel aman. Misalnya, coba jalankan A='foo"bar' echo $A | sed s/$A/baz/di bash. Kutipan ganda diperlakukan seperti 'foo' dan 'bar' di sekitarnya
Pianosaurus
92

Perintah sed memungkinkan Anda untuk menggunakan karakter lain alih-alih /sebagai pemisah:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Kutipan ganda tidak masalah.

scre_www
sumber
5
Anda masih perlu melarikan diri .yang sebaliknya memiliki arti khusus. Saya mengedit jawaban Anda.
ypid
Saya baru saja mencoba melakukan: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filedengan sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filedan itu tidak melakukan hal yang sama.
Dimitri Kopriwa
1
Karena ini hanya berlaku untuk pengganti, ini harus mengatakan: sPerintah (seperti dalam pengganti) sed memungkinkan Anda untuk menggunakan karakter lain alih-alih / sebagai pemisah. Juga, ini akan menjadi jawaban untuk bagaimana menggunakan sed pada URL dengan karakter garis miring. Itu tidak menjawab pertanyaan OP bagaimana melepaskan string yang dimasukkan oleh pengguna, yang bisa berisi /, \, tetapi juga # jika Anda memutuskan untuk menggunakannya. Dan selain itu, URI dapat berisi # terlalu
papo
2
itu mengubah hidup saya! Terima kasih!
Franciscon Santos
48

Hanya tiga karakter literal yang diperlakukan secara khusus dalam klausa penggantian adalah /(untuk menutup klausa), \(untuk menghindari karakter, backreference, & c.), Dan &(untuk memasukkan kecocokan dalam penggantian). Karena itu, yang perlu Anda lakukan adalah melarikan diri dari ketiga karakter:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Contoh:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
Ben Blank
sumber
Juga baris baru, saya pikir. Bagaimana cara saya keluar dari baris baru?
Alexander Gladysh
2
Hati-hati apa perilaku default gema sehubungan dengan garis miring terbalik. Dalam bash, gema default untuk tidak ada interpretasi dari backslash lolos, yang melayani tujuan di sini. Di dash (sh), di sisi lain, gema menafsirkan backslash lolos dan tidak memiliki cara, sejauh yang saya tahu, menekan ini. Oleh karena itu, dalam tanda hubung (sh), alih-alih gema $ x, lakukan printf '% s \ n' $ x.
Youssef Eldakar
Juga, selalu gunakan opsi -r ketika membaca untuk memperlakukan backslash dalam input pengguna sebagai literal.
Youssef Eldakar
Untuk kompatibilitas lintas platform dengan cangkang lain, Anda harus membaca dokumen ini mengenai penggantian karakter khusus: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton
2
@Drux Tiga karakter adalah satu-satunya yang istimewa dalam klausa ganti . Jauh lebih khusus dalam klausa pola.
lenz
33

Berdasarkan ekspresi reguler Pianosaurus, saya membuat fungsi bash yang lolos dari kata kunci dan penggantian.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Begini cara Anda menggunakannya:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
Gurpartap Singh
sumber
3
Terima kasih! jika ada orang lain yang mendapatkan kesalahan sintaks ketika mencoba menggunakannya, sama seperti saya, ingatlah untuk menjalankannya menggunakan bash, bukan sh
Konstantin Pereiaslov
1
Apakah ada fungsi hanya untuk menghindari string untuk sed daripada membungkus sed?
CMCDragonkai
Hai, hanya peringatan umum tentang memulai pipa dengan gema seperti ini: Beberapa (sebagian?) Implementasi opsi gema mengambil (lihat man echo), menyebabkan pipa untuk berperilaku tak terduga ketika argumen Anda $1dimulai dengan tanda hubung. Sebagai gantinya, Anda dapat memulai pipa Anda dengan printf '%s\n' "$1".
Pianosaurus
17

Agak terlambat untuk merespons ... tetapi ADA cara yang lebih sederhana untuk melakukan ini. Ubah saja pembatas (yaitu, karakter yang memisahkan bidang). Jadi, alih-alih s/foo/bar/Anda menulis s|bar|foo.

Dan, inilah cara mudah untuk melakukan ini:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

Output yang dihasilkan tidak memiliki klausa DEFINER yang jahat itu.

pengguna2460464
sumber
10
Tidak, &dan `` masih harus diloloskan, seperti juga pembatas, yang dipilih.
mirabilos
3
Itu memecahkan masalah saya, karena saya memiliki "/" karakter dalam string pengganti. Terima kasih sobat!
Evgeny Goldin
bekerja untukku. Apa yang saya lakukan adalah mencoba melarikan diri $dalam string yang akan diubah, dan mempertahankan makna $dalam string pengganti. katakanlah saya ingin mengubah $XXXke nilai variabel $YYY, sed -i "s|\$XXX|$YYY|g" fileberfungsi dengan baik.
hakunami
11

Ternyata Anda mengajukan pertanyaan yang salah. Saya juga mengajukan pertanyaan yang salah. Alasannya salah adalah awal dari kalimat pertama: "Dalam skrip bash saya ...".

Saya memiliki pertanyaan yang sama & membuat kesalahan yang sama. Jika Anda menggunakan bash, Anda tidak perlu menggunakan sed untuk melakukan penggantian string (dan jauh lebih bersih untuk menggunakan fitur ganti yang dibangun menjadi bash).

Alih-alih sesuatu seperti, misalnya:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

Anda dapat menggunakan fitur bash secara eksklusif:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
destenson
sumber
BTW, menyoroti sintaks di sini salah. Kutipan eksterior cocok & kutipan interior cocok. Dengan kata lain, sepertinya $Adan $Btidak dikutip, tetapi sebenarnya tidak. Kutipan di dalam ${}tidak cocok dengan kutipan di luarnya.
destenson
Anda sebenarnya tidak perlu mengutip sisi kanan dari suatu tugas (kecuali jika Anda ingin melakukan sesuatu seperti var='has space') - OUTPUT=${INPUT//"$A"/"$B"}aman.
Benjamin W.
Anda sebenarnya tidak perlu mengutip sisi kanan dari suatu tugas (kecuali jika Anda ingin itu bekerja di dunia nyata dan bukan hanya sebagai skrip mainan untuk menunjukkan sketsa Anda). Saya selalu mencoba untuk mengutip setiap ekspansi variabel yang saya tidak ingin shell untuk menafsirkan, kecuali saya punya alasan khusus untuk tidak melakukannya. Dengan begitu, segala sesuatunya cenderung tidak terlalu sering rusak, terutama ketika diberikan input baru atau tidak terduga.
destenson
1
Lihat manual : "Semua nilai mengalami ekspansi tilde, ekspansi parameter dan variabel, penggantian perintah, ekspansi aritmatika, dan penghapusan kutipan (dirinci di bawah)." Yaitu, sama seperti dalam tanda kutip ganda.
Benjamin W.
1
Bagaimana jika Anda perlu menggunakan sed pada file?
Efren
1

Gunakan awk - lebih bersih:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare
greggster
sumber
2
Masalahnya awkadalah tidak ada yang mirip dengan itu sed -i, yang sangat berguna 99% dari waktu.
Tino
Ini adalah langkah ke arah yang benar, tetapi awk masih menginterpretasikan beberapa karakter meta di substitusi Anda, jadi itu masih tidak aman untuk input pengguna.
Jeremy Huiskamp
0

Ini adalah contoh AWK yang saya gunakan beberapa waktu lalu. Ini adalah AWK yang mencetak AWKS baru. AWK dan SED yang serupa mungkin merupakan templat yang baik.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Kelihatannya berlebihan, tetapi entah bagaimana kombinasi dari kutipan berfungsi untuk menjaga 'dicetak sebagai literal. Maka jika saya ingat dengan benar, vaiables hanya dikelilingi dengan tanda kutip seperti ini: "$ 1". Cobalah, beri tahu saya cara kerjanya dengan SED.

Alex
sumber
0

Saya memiliki peningkatan atas fungsi sedeasy, yang AKAN putus dengan karakter khusus seperti tab.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Jadi, apa bedanya? $1dan $2dibungkus dengan tanda kutip untuk menghindari ekspansi shell dan mempertahankan tab atau spasi ganda.

Perpipaan tambahan | sed -e 's:\t:\\t:g'(saya suka :sebagai token) yang mengubah tab di \t.

Francisco De Zuviria
sumber
Tetapi lihat komentar saya pada jawaban sedeasy tentang menggunakan gema dalam pipa.
Pianosaurus
0

Ini adalah kode pelarian yang saya temukan:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e
Ark25
sumber
-1

jangan lupa semua kesenangan yang terjadi dengan batasan shell di sekitar "dan '

jadi (dalam ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"
NeronLeVelu
sumber
persis arah yang saya butuhkan, untuk menghindari hasil pencarian, ditemukan melalui google jadi mungkin bermanfaat untuk seseorang - diakhiri dengan - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg
-1

Jika Anda hanya ingin mengganti nilai Variabel dalam perintah sed maka hapus saja Contoh:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
Shailender Singh
sumber
-2

Jika Anda menggunakan kata sandi acak untuk sedmengganti untuk mengganti pola, maka Anda memilih untuk berhati-hati tentang set karakter mana dalam string acak. Jika Anda memilih kata sandi yang dibuat dengan menyandikan nilai sebagai base64, maka hanya ada karakter yang dimungkinkan pada base64 dan juga karakter khusus dalam sedpola ganti. Karakter itu adalah "/", dan mudah dihapus dari kata sandi yang Anda hasilkan:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;
Mark Stosberg
sumber
-4

Cara yang lebih mudah untuk melakukan ini adalah dengan membangun string sebelum tangan dan menggunakannya sebagai parameter untuk sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt
Javonne Martin
sumber
Gagal dan sangat berbahaya, karena REPLACE adalah pengguna disediakan: REPLACE=/memberikansed: -e expression #1, char 12: unknown option to `s'
Tino