Karakter mana yang harus diloloskan saat menggunakan Bash?

206

Apakah ada daftar karakter lengkap yang perlu diloloskan di Bash? Bisakah diperiksa hanya dengansed ?

Secara khusus, saya sedang memeriksa apakah %perlu melarikan diri atau tidak. Saya mencoba

echo "h%h" | sed 's/%/i/g'

dan bekerja dengan baik, tanpa melarikan diri %. Apakah itu berarti% tidak perlu melarikan diri? Apakah ini cara yang baik untuk memeriksa keperluannya?

Dan lebih umum: mereka karakter yang sama untuk melarikan diri di shelldan bash?

fedorqui 'SO berhenti merugikan'
sumber
4
Secara umum, jika Anda peduli, Anda Melakukannya Salah. Menangani data tidak boleh melibatkan menjalankannya melalui proses parsing dan evaluasi yang digunakan untuk kode, membuat melarikan diri diperdebatkan. Ini adalah paralel yang sangat dekat dengan praktik terbaik untuk SQL - di mana Hal yang Benar adalah dengan menggunakan variabel bind dan Hal yang Salah adalah mencoba "membersihkan" data yang disuntikkan melalui pergantian string.
Charles Duffy
Terkait dengan stackoverflow.com/questions/2854655/…
skywinder
8
@ CharlesDuffy Ya, tapi kadang-kadang apa yang dilakukan mesin pernyataan disiapkan di backend hanya melarikan diri. Apakah SO "melakukan kesalahan" karena mereka lolos dari komentar yang dikirimkan pengguna sebelum menampilkannya di browser? Tidak. Mereka mencegah XSS. Sama sekali tidak peduli berarti melakukan kesalahan.
Parthian Shot
@ ParthianShot, jika mesin pernyataan yang disiapkan tidak membuat data benar-benar keluar dari band dari kode, orang-orang yang menulisnya harus ditembak. Ya, saya tahu protokol kawat MySQL diimplementasikan seperti itu; pernyataan saya berdiri.
Charles Duffy
@CharlesDuffy Dan poin saya- bahwa kadang-kadang pilihan Anda adalah membuat sesuatu bekerja dengan aman menggunakan toolchain yang akan membuat ngeri purist, atau tenggelam delapan kali waktu dan upaya untuk membuatnya cantik- juga masih berdiri.
Parthian Shot

Jawaban:

282

Ada dua aturan mudah dan aman yang berfungsi tidak hanya di dalamnya shtetapi juga bash.

1. Masukkan seluruh string dalam tanda kutip tunggal

Ini berfungsi untuk semua karakter kecuali kutipan tunggal itu sendiri. Untuk menghindari kutip tunggal, tutup kutipan sebelumnya, masukkan kutip tunggal, dan buka kembali kutipan.

'I'\''m a s@fe $tring which ends in newline
'

perintah sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Melarikan diri setiap char dengan backslash

Ini berfungsi untuk semua karakter kecuali baris baru. Untuk karakter baris baru gunakan tanda kutip tunggal atau ganda. String kosong masih harus ditangani - ganti dengan""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

perintah sed: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Versi 2 yang lebih mudah dibaca

Ada serangkaian karakter yang aman dan mudah, seperti [a-zA-Z0-9,._+:@%/-], yang dapat dibiarkan tanpa hambatan agar lebih mudah dibaca

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

perintah sed: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Perhatikan bahwa dalam program sed, seseorang tidak dapat mengetahui apakah baris input terakhir berakhir dengan byte baris baru (kecuali ketika kosong). Itu sebabnya kedua perintah sed di atas menganggap itu tidak. Anda dapat menambahkan baris baru yang dikutip secara manual.

Perhatikan bahwa variabel shell hanya didefinisikan untuk teks dalam arti POSIX. Memproses data biner tidak ditentukan. Untuk implementasi yang penting, biner bekerja dengan pengecualian byte NUL (karena variabel diimplementasikan dengan string C, dan dimaksudkan untuk digunakan sebagai string C, yaitu argumen program), tetapi Anda harus beralih ke lokal "biner" seperti latin1 .


(Anda dapat dengan mudah memvalidasi aturan dengan membaca spesifikasi POSIX sh. Untuk bash, periksa manual referensi yang ditautkan oleh @AustinPhillips)

Jo So
sumber
Catatan: variasi yang baik pada # 1 dapat dilihat di sini: github.com/scop/bash-completion/blob/… . Itu tidak perlu berjalan sed, tetapi membutuhkan bash.
jwd
4
Catatan untuk orang lain (seperti saya!) Yang berjuang untuk membuatnya berfungsi .... sepertinya rasa sed yang Anda dapatkan di OSX tidak menjalankan perintah sed ini dengan benar. Mereka bekerja dengan baik di Linux!
dalelane
@dalelane: Tidak bisa menguji di sini. Harap edit ketika Anda memiliki versi yang berfungsi pada keduanya.
Jo So
Sepertinya Anda melewatkan-haruskah string dimulai dengan '-' (minus), atau apakah itu hanya berlaku untuk nama file? - dalam kasus terakhir membutuhkan tanda './' di depan.
slashmais
Saya tidak yakin apa yang Anda maksud. Dengan perintah-perintah sed, string input diambil dari stdin.
Jo So
59

format yang dapat digunakan kembali sebagai input shell

Ada arahan format khusus printf ( %q) yang dibuat untuk permintaan semacam ini:

printf [-v var] memformat [argumen]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Beberapa sampel:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Ini dapat digunakan melalui variabel juga:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Pemeriksaan cepat dengan semua (128) byte ascii:

Perhatikan bahwa semua byte dari 128 hingga 255 harus di-escape.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Ini harus membuat sesuatu seperti:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Di mana bidang pertama adalah nilai hexa byte, berisi kedua E jika karakter harus di-escape dan field ketiga menampilkan lolos dari karakter.

Mengapa ,?

Anda dapat melihat beberapa karakter yang tidak selalu harus diloloskan, seperti ,, }dan {.

Jadi tidak selalu tetapi kadang-kadang :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

atau

echo test { 1, 2, 3 }
test { 1, 2, 3 }

tapi peduli:

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 
F. Hauri
sumber
Ini memiliki masalah yang, memanggil pritnf melalui bash / sh, string harus terlebih dahulu diloloskan ke shell untuk bash / sh
ThorSummoner
1
@ Thormonmoner, tidak jika Anda meneruskan string sebagai argumen literal ke shell dari bahasa yang berbeda (di mana Anda mungkin sudah tahu cara mengutip). Dalam Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()akan memberi Anda versi shell yang dikutip dengan benar arbitrary_string.
Charles Duffy
1
FYI bash %qrusak untuk waktu yang lama - Jika pikiran saya melayani saya dengan baik, kesalahan telah diperbaiki (tapi mungkin masih rusak) pada 2013 setelah rusak selama ~ 10 tahun. Jadi jangan mengandalkan itu.
Jo So
@CharlesDuffy Tentu saja, begitu Anda berada di tanah Python, shlex.quote()(> = 3.3, pipes.quote()- tidak berdokumen - untuk versi yang lebih lama) juga akan melakukan pekerjaan dan menghasilkan versi yang lebih dapat dibaca manusia (menambahkan kutipan dan melarikan diri, jika perlu) dari sebagian besar string, tanpa perlu menelurkan shell.
Thomas Perl
1
Terima kasih telah menambahkan catatan khusus tentang ,. Saya terkejut mengetahui bahwa bawaan Bash printf -- %q ','memberi \,, tetapi /usr/bin/printf -- %q ','memberi ,(tanpa escrow). Sama untuk karakter lainnya: {, |, }, ~.
kevinarpe
34

Untuk menyelamatkan orang lain dari keharusan ke RTFM ... di bash :

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

... jadi jika Anda lolos dari itu (dan kutipannya sendiri, tentu saja) Anda mungkin baik-baik saja.

Jika Anda mengambil pendekatan yang lebih konservatif 'ketika ragu-ragu, lepas dari itu', Anda harus menghindari karakter yang memiliki makna khusus dengan tidak melarikan diri dari karakter pengidentifikasi (mis., Huruf ASCII, angka, atau '_'). Sangat tidak mungkin ini akan pernah (yaitu dalam beberapa shell POSIX-ish aneh) memiliki arti khusus dan dengan demikian perlu melarikan diri.

Matius
sumber
1
di sini adalah manual yang dikutip di atas: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
Ini adalah jawaban singkat, manis dan sebagian besar benar (+1 untuk itu) tapi mungkin lebih baik menggunakan tanda kutip tunggal - lihat jawaban saya yang lebih panjang.
Jo So
26

Dengan menggunakan print '%q' teknik ini , kita dapat menjalankan loop untuk mengetahui karakter mana yang spesial:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Ini memberikan hasil ini:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Beberapa hasil, seperti ,terlihat agak mencurigakan. Akan menarik untuk mendapatkan input @ CharlesDuffy tentang ini.

codeforester
sumber
2
Anda dapat membaca jawaban untuk ,terlihat sedikit curiga pada paragraf terakhir jawaban saya
F. Hauri
2
Ingatlah bahwa %qAnda tidak tahu di mana di dalam shell Anda berencana untuk menggunakan karakter, jadi itu akan luput dari semua karakter yang dapat memiliki makna khusus dalam setiap konteks shell yang mungkin. ,itu sendiri tidak memiliki arti khusus untuk dia shell tetapi sebagai @ F.Hauri telah menunjukkan dalam jawabannya, itu memang memiliki makna khusus dalam {...}ekspansi brace: gnu.org/savannah-checkouts/gnu/bash/manual/… Ini seperti! yang juga hanya membutuhkan ekspansi dalam situasi tertentu, tidak secara umum: echo Hello World!berfungsi dengan baik, namun echo test!testakan gagal.
Mecki
18

Karakter yang perlu melarikan diri berbeda di shell Bourne atau POSIX dari Bash. Umumnya (sangat) Bash adalah superset dari kerang-kerang itu, jadi apa pun yang Anda lewatkanshell harus dilepaskan di Bash.

Aturan umum yang bagus adalah "jika ragu-ragu, hindarilah". Tetapi melarikan diri beberapa karakter memberi mereka makna khusus, seperti \n. Ini tercantum di man bashhalaman di bawah Quotingdan echo.

Selain itu, lepas dari karakter apa pun yang bukan alfanumerik, itu lebih aman. Saya tidak tahu daftar tunggal yang pasti.

Halaman manual mencantumkan semuanya di suatu tempat, tetapi tidak di satu tempat. Belajar bahasa, itu cara untuk memastikan.

Salah satu yang menarik saya adalah !. Ini adalah karakter khusus (ekspansi sejarah) di Bash (dan csh) tetapi tidak di shell Korn. Bahkan echo "Hello world!"memberi masalah. Menggunakan tanda kutip tunggal, seperti biasa, menghilangkan makna khusus.

cdarke
sumber
1
Saya secara khusus menyukai saran umum yang bagus adalah "jika ragu, lupakan saja" saran. Masih ragu apakah memeriksa dengan sedcukup baik untuk melihat apakah harus lolos. Terima kasih atas jawaban anda!
fedorqui 'SO berhenti merugikan'
2
@ fedorqui: Memeriksa dengan sedtidak perlu, Anda dapat memeriksa dengan hampir semua hal. sedbukan masalahnya, bashadalah. Di dalam tanda kutip tunggal tidak ada karakter khusus (kecuali tanda kutip tunggal), Anda bahkan tidak dapat melarikan diri karakter di sana. Sebuah sedperintah biasanya harus berada di dalam tanda kutip tunggal karena metakarakter RE memiliki terlalu banyak tumpang tindih dengan metakarakter shell aman. Pengecualian adalah ketika menanamkan variabel shell, yang harus dilakukan dengan hati-hati.
cdarke
5
Periksa dengan echo. Jika Anda mendapatkan apa yang Anda masukkan, itu tidak perlu melarikan diri. :)
Mark Reed
6

Saya kira Anda berbicara tentang string bash. Ada beberapa tipe string yang memiliki serangkaian persyaratan berbeda untuk melarikan diri. misalnya. String kutipan tunggal berbeda dengan string kutipan ganda.

Referensi terbaik adalah Quoting manual bash.

Ini menjelaskan karakter mana yang harus diloloskan. Perhatikan bahwa beberapa karakter mungkin perlu keluar tergantung pada opsi mana yang diaktifkan seperti ekspansi riwayat.

Austin Phillips
sumber
3
Jadi itu menegaskan bahwa melarikan diri adalah hutan tanpa solusi yang mudah, harus memeriksa setiap kasus. Terima kasih!
fedorqui 'SO stop harming'
@ fedorqui Seperti bahasa apa pun, ada seperangkat aturan yang harus diikuti. Untuk bash string yang lolos, himpunan aturan cukup kecil seperti yang dijelaskan dalam manual. String termudah untuk digunakan adalah tanda kutip tunggal karena tidak ada yang perlu melarikan diri. Namun, tidak ada cara untuk memasukkan satu kutipan dalam string tunggal yang dikutip.
Austin Phillips
@fedorqui. Itu bukan hutan. Melarikan diri cukup bisa dilakukan. Lihat posting baru saya.
Jo So
@ fedorqui Anda tidak dapat menggunakan kutipan tunggal di dalam string yang dikutip tunggal tetapi Anda dapat "melarikan diri" dengan sesuatu seperti: 'text' "'' 'more text'
CR.
4

Saya perhatikan bahwa bash secara otomatis keluar dari beberapa karakter saat menggunakan pelengkapan otomatis.

Misalnya, jika Anda memiliki direktori yang bernama dir:A, bash akan otomatis dilengkapi kedir\:A

Menggunakan ini, saya menjalankan beberapa percobaan menggunakan karakter dari tabel ASCII dan membuat daftar berikut:

Karakter yang kabur lolos pada lengkapi-otomatis : (termasuk spasi)

 !"$&'()*,:;<=>?@[\]^`{|}

Karakter yang bash tidak luput :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Saya mengecualikan /, karena tidak dapat digunakan dalam nama direktori)

yuri
sumber
2
Jika Anda benar-benar ingin memiliki daftar komprehensif, saya sarankan melihat karakter mana yang printf %qtidak dan tidak dimodifikasi jika dilewatkan sebagai argumen - idealnya, melalui seluruh rangkaian karakter.
Charles Duffy
Ada contoh di mana bahkan dengan string tanda kutip, Anda mungkin ingin melarikan diri huruf dan angka untuk menghasilkan karakter khusus. Misalnya: tr '\ n' '\ t' yang menerjemahkan karakter baris baru menjadi karakter tab.
Dick Guertin