Aturan umum dalam skrip shell adalah bahwa variabel harus selalu dikutip kecuali ada alasan kuat untuk tidak melakukannya. Untuk perincian lebih dari yang mungkin ingin Anda ketahui, lihat T&J besar ini: Implikasi keamanan lupa mengutip variabel dalam bash / POSIX shells .
Namun pertimbangkan fungsi seperti berikut:
run_this(){
$@
}
Haruskah $@
dikutip di sana atau tidak? Saya bermain dengannya sebentar dan tidak dapat menemukan kasus di mana kurangnya kutipan menyebabkan masalah. Di sisi lain, menggunakan tanda kutip membuatnya rusak ketika melewati perintah yang berisi spasi sebagai variabel yang dikutip:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
Menjalankan skrip di atas menghasilkan:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
Saya dapat menyiasatinya jika saya menggunakan run_that $comm
alih-alih run_that "$comm"
, tetapi karena fungsi run_this
(yang tidak dikutip) berfungsi dengan keduanya, sepertinya ini adalah taruhan yang lebih aman.
Jadi, dalam kasus khusus menggunakan $@
dalam fungsi yang tugasnya mengeksekusi $@
sebagai perintah, harus $@
dikutip? Tolong jelaskan mengapa itu harus / tidak boleh dikutip dan berikan contoh data yang dapat merusaknya.
sumber
run_that
Perilaku pasti apa yang saya harapkan (bagaimana jika ada ruang di jalan menuju perintah?). Jika Anda menginginkan perilaku lain, tentunya Anda akan berhenti tanda kutip di situs- panggilan tempat Anda mengetahui data apa itu? Saya berharap untuk memanggil fungsi ini sebagairun_that ls -l
, yang berfungsi sama di kedua versi. Apakah ada kasus yang membuat Anda berharap berbeda?${mycmd[@]}
.Jawaban:
Masalahnya terletak pada bagaimana perintah dilewatkan ke fungsi:
"$@"
harus digunakan dalam kasus umum di manarun_this
fungsi Anda diawali dengan perintah yang biasanya ditulis.run_this
mengarah ke mengutip neraka:Saya tidak yakin bagaimana cara meloloskan nama file dengan spasi
run_this
.sumber
run_this
.$@
tanpa sengaja. Saya harus memberikan contoh. : Drun_this
. Ini pada dasarnya adalah masalah yang sama Anda mengalami dengan memasukkan perintah kompleks ke dalam string seperti yang dibahas dalam Bash FAQ 050 .Itu baik:
Atau:
atau:
Tapi:
Tidak masuk akal.
Jika Anda ingin menjalankan
ls -l
perintah (bukanls
perintah denganls
dan-l
sebagai argumen), Anda harus melakukan:Tetapi jika (lebih mungkin), itu adalah
ls
perintah denganls
dan-l
sebagai argumen, Anda akan menjalankan:Sekarang, jika lebih dari sekedar perintah sederhana yang ingin Anda jalankan, jika Anda ingin melakukan penugasan variabel, pengalihan, pipa ..., hanya
interpret_this_shell_code
akan melakukan:meskipun tentu saja Anda selalu dapat melakukannya:
sumber
Melihatnya dari perspektif bash / ksh / zsh,
$*
dan$@
merupakan kasus khusus ekspansi array umum. Ekspansi array tidak seperti ekspansi variabel normal:Dengan
$*
/${a[*]}
ekspansi, Anda membuat array digabungkan dengan nilai pertamaIFS
—yang secara default adalah ruang — menjadi satu string raksasa. Jika Anda tidak mengutipnya, ia akan terpecah seperti string normal.Dengan
$@
/${a[@]}
ekspansi, perilaku tergantung pada apakah$@
/${a[@]}
ekspansi dikutip atau tidak:"$@"
atau"${a[@]}"
), Anda mendapatkan yang setara dengan"$1" "$2" "$3" #...
atau"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
atau${a[@]}
) Anda mendapatkan yang setara dengan$1 $2 $3 #...
atau${a[1]} ${a[2]} ${a[3]} # ...
Untuk perintah pembungkus, Anda pasti menginginkan ekspansi @ dikutip (1.).
Lebih banyak info bagus tentang array bash (dan mirip bash): https://lukeshu.com/blog/bash-arrays.html
sumber
Karena ketika Anda tidak menggandakan penawaran
$@
, Anda meninggalkan semua masalah globbing di tautan yang Anda berikan ke fungsi Anda.Bagaimana Anda bisa menjalankan perintah yang bernama
*
? Anda tidak dapat melakukannya denganrun_this
:Dan Anda lihat, bahkan ketika kesalahan terjadi,
run_that
memberi Anda pesan yang lebih bermakna.Satu-satunya cara untuk memperluas
$@
ke kata-kata individual adalah kutip ganda. Jika Anda ingin menjalankannya sebagai perintah, Anda harus meneruskan perintah dan parameternya sebagai kata-kata yang terpisah. Bahwa hal yang Anda lakukan di sisi penelepon, bukan di dalam fungsi Anda.adalah pilihan yang lebih baik. Atau jika array dukungan shell Anda:
Bahkan ketika shell sama sekali tidak mendukung array, Anda masih bisa memainkannya dengan menggunakan
"$@"
.sumber
Mengeksekusi variabel dalam
bash
adalah teknik rawan kegagalan. Tidak mungkin untuk menulisrun_this
fungsi yang menangani semua tepi case dengan benar, seperti:ls | grep filename
)ls > /dev/null
)if
while
dll.Jika semua yang ingin Anda lakukan adalah menghindari pengulangan kode, Anda lebih baik menggunakan fungsi. Misalnya, alih-alih:
Anda harus menulis
Jika perintah hanya tersedia pada saat run time, Anda harus menggunakan
eval
, yang secara khusus dirancang untuk menangani semua kebiasaan yang akan membuatrun_this
gagal:Perhatikan bahwa
eval
ini dikenal untuk masalah keamanan, tetapi jika Anda melewatkan variabel dari sumber terpercaya untukrun_this
, Anda akan menghadapi eksekusi kode sewenang-wenang sama dengan baik.sumber
Pilihan ada padamu. Jika Anda tidak mengutip
$@
salah satu nilainya, perluas ekspansi dan interpretasi tambahan. Jika Anda mengutipnya semua argumen yang dilewati, fungsi tersebut direproduksi dalam perluasannya kata demi kata. Anda tidak akan pernah dapat menangani token sintaksis shell secara andal seperti&>|
dan lain-lain dengan cara apa pun tanpa menguraikan argumen sendiri - dan karenanya Anda memiliki pilihan yang lebih masuk akal untuk menyerahkan fungsi Anda, salah satunya:"$@"
....atau...
$@
.Tidak ada cara yang salah jika disengaja dan jika efek dari apa yang Anda pilih dipahami dengan baik. Kedua cara memiliki keunggulan satu di atas yang lain, meskipun keunggulan yang kedua jarang bermanfaat. Masih...
... tidak sia - sia , hanya saja jarang berguna . Dan dalam sebuah
bash
shell, karenabash
tidak secara default menempel definisi variabel ke lingkungannya bahkan ketika definisi tersebut ditambahkan ke baris perintah dari builtin khusus atau ke suatu fungsi, nilai global untuk$IFS
tidak terpengaruh, dan deklarasi adalah lokal hanya untukrun_this()
panggilan.Demikian pula:
... globbing juga dapat dikonfigurasi. Kutipan melayani tujuan - mereka bukan untuk apa-apa. Tanpa mereka ekspansi shell mengalami interpretasi ekstra - interpretasi yang dapat dikonfigurasi . Dulu - dengan beberapa sangat tua kerang - yang
$IFS
telah secara global diterapkan untuk semua masukan, dan bukan hanya ekspansi. Bahkan, kata shell berperilaku sangat sepertirun_this()
dalam hal mereka memecahkan semua kata input pada nilai$IFS
. Jadi, jika yang Anda cari adalah perilaku shell yang sangat lama, maka Anda harus menggunakannyarun_this()
.Saya tidak mencarinya, dan saya agak kesulitan saat ini untuk memberikan contoh yang berguna. Saya biasanya lebih suka untuk perintah shell saya berjalan untuk menjadi yang saya ketik. Maka, diberi pilihan, saya hampir selalu
run_that()
. Kecuali itu...Apa saja bisa dikutip. Perintah akan dijalankan dengan mengutip. Ini bekerja karena pada saat perintah itu benar-benar dijalankan, semua kata input telah mengalami penghapusan kutipan - yang merupakan tahap terakhir dari proses interpretasi input shell. Jadi perbedaan antara
'ls'
danls
hanya bisa berarti ketika shell sedang menerjemahkan - dan itulah sebabnya mengutipls
memastikan bahwa setiap alias yang bernamals
tidak diganti denganls
kata perintah yang saya kutip . Selain itu, satu-satunya hal yang mempengaruhi kutipan adalah pembatasan kata-kata (yang adalah bagaimana dan mengapa variabel / input-spasi putih mengutip bekerja) , dan interpretasi dari metakarakter dan kata-kata yang dicadangkan.Begitu:
Anda tidak akan pernah bisa melakukan itu dengan salah satu
run_this()
ataurun_that()
.Tetapi nama fungsi, atau
$PATH
perintah, atau builtin akan mengeksekusi dengan baik dikutip atau tidak dikutip, dan itulah bagaimanarun_this()
danrun_that()
bekerja di tempat pertama. Anda tidak akan dapat melakukan sesuatu yang bermanfaat dengan$<>|&(){}
semua itu. Singkatnyaeval
, adalah.Tetapi tanpa itu, Anda dibatasi pada batas-batas perintah sederhana berdasarkan kutipan yang Anda gunakan (bahkan ketika Anda tidak melakukannya karena
$@
bertindak seperti kutipan pada awal proses ketika perintah diuraikan untuk karakter metakarakter) . Kendala yang sama berlaku untuk penugasan dan pengalihan baris perintah, yang terbatas pada baris perintah fungsi. Tapi itu bukan masalah besar:Saya dapat dengan mudah
<
mengarahkan ulang input atau>
output di sana seperti saya membuka pipa.Pokoknya, secara berputar-putar, tidak ada cara yang benar atau salah di sini - setiap cara memiliki kegunaannya. Hanya saja Anda harus menulisnya saat Anda bermaksud menggunakannya, dan Anda harus tahu apa yang ingin Anda lakukan. Menghilangkan tanda kutip dapat memiliki tujuan - jika tidak akan ada tanda kutip sama sekali - tetapi jika Anda menghilangkannya karena alasan yang tidak relevan dengan tujuan Anda, Anda hanya menulis kode yang buruk. Lakukan apa yang Anda maksudkan; Saya tetap mencoba.
sumber