$ ls -l /tmp/test/my\ dir/
total 0
Saya bertanya-tanya mengapa cara-cara berikut untuk menjalankan perintah di atas gagal atau berhasil?
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Jawaban:
Ini telah dibahas dalam sejumlah pertanyaan di unix.SE, saya akan mencoba untuk mengumpulkan semua masalah yang bisa saya kemukakan di sini. Referensi di bagian akhir.
Kenapa gagal?
Alasan Anda menghadapi masalah tersebut adalah pemisahan kata dan fakta bahwa tanda kutip diperluas dari variabel tidak bertindak sebagai tanda kutip, tetapi hanya karakter biasa.
Kasus-kasus yang disajikan dalam pertanyaan:
Di sini,
$abc
terbelah, danls
dapatkan dua argumen"/tmp/test/my
dandir"
(dengan tanda kutip di depan yang pertama dan belakang yang kedua):Di sini, ekspansi dikutip, jadi disimpan sebagai satu kata. Shell mencoba untuk menemukan program yang disebut
ls -l "/tmp/test/my dir"
, spasi dan kutipan disertakan.Dan di sini, hanya kata pertama atau
$abc
diambil sebagai argumen untuk-c
, jadi Bash hanya berjalanls
di direktori saat ini. Dengan kata lain argumen untuk bash, dan digunakan untuk mengisi$0
,$1
dllDengan
bash -c "$abc"
, daneval "$abc"
, ada langkah pemrosesan shell tambahan, yang membuat kutipan berhasil, tetapi juga menyebabkan semua ekspansi shell diproses lagi , jadi ada risiko tidak sengaja menjalankan ekspansi perintah dari data yang disediakan pengguna, kecuali jika Anda sangat hati-hati mengutip.Cara yang lebih baik untuk melakukannya
Dua cara yang lebih baik untuk menyimpan perintah adalah a) menggunakan fungsi sebagai gantinya, b) menggunakan variabel array (atau parameter posisi).
Menggunakan fungsi:
Cukup mendeklarasikan fungsi dengan perintah di dalamnya, dan jalankan fungsi seolah-olah itu adalah perintah. Ekspansi dalam perintah di dalam fungsi hanya diproses ketika perintah dijalankan, bukan ketika itu didefinisikan, dan Anda tidak perlu mengutip perintah individu.
Menggunakan array:
Array memungkinkan pembuatan variabel multi-kata di mana setiap kata berisi ruang putih. Di sini, kata-kata individual disimpan sebagai elemen array yang berbeda, dan
"${array[@]}"
ekspansi memperluas setiap elemen sebagai kata-kata shell yang terpisah:Sintaksnya sedikit mengerikan, tetapi array juga memungkinkan Anda untuk membangun baris perintah sepotong demi sepotong. Sebagai contoh:
atau biarkan bagian-bagian dari baris perintah konstan dan gunakan array isi hanya sebagian saja, opsi atau nama file:
Kelemahan dari array adalah bahwa mereka bukan fitur standar, jadi shell POSIX sederhana (seperti
dash
, default/bin/sh
di Debian / Ubuntu) tidak mendukung mereka (tetapi lihat di bawah). Namun, bash, ksh dan zsh melakukannya, sehingga kemungkinan sistem Anda memiliki beberapa shell yang mendukung array.Menggunakan
"$@"
Dalam shells tanpa dukungan untuk array bernama, kita masih dapat menggunakan parameter posisi (pseudo-array
"$@"
) untuk menyimpan argumen dari perintah.Berikut ini harus bit script portabel yang melakukan setara dengan bit kode di bagian sebelumnya. Array diganti dengan
"$@"
, daftar parameter posisi. Pengaturan"$@"
dilakukan denganset
, dan tanda kutip ganda di sekitar"$@"
adalah penting (ini menyebabkan elemen daftar dikutip secara individual).Pertama, cukup menyimpan perintah dengan argumen di
"$@"
dan menjalankannya:Pengaturan bagian opsi baris perintah untuk suatu perintah:
Hanya menggunakan
"$@"
untuk opsi dan operan:(Tentu saja,
"$@"
biasanya diisi dengan argumen untuk skrip itu sendiri, jadi Anda harus menyimpannya di suatu tempat sebelum bertujuan ulang"$@"
.)Hati-hati dengan
eval
!Saat
eval
memperkenalkan tingkat penawaran tambahan dan pemrosesan ekspansi, Anda harus berhati-hati dengan input pengguna. Misalnya, ini berfungsi selama pengguna tidak mengetikkan tanda kutip tunggal:Tetapi jika mereka memberikan input
'$(uname)'.txt
, skrip Anda dengan senang hati menjalankan substitusi perintah.Sebuah versi dengan array kebal terhadap hal itu karena kata-kata disimpan terpisah sepanjang waktu, tidak ada kutipan atau pemrosesan lain untuk konten
filename
.Referensi
sumber
cmd="ls -l $(printf "%q" "$filename")"
. tidak cantik, tetapi jika pengguna mati untuk menggunakaneval
, itu membantu. Ini juga sangat berguna untuk mengirimkan perintah meskipun hal serupa, sepertissh foohost "ls -l $(printf "%q" "$filename")"
, atau dalam sprit pertanyaan ini:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
Cara teraman untuk menjalankan perintah (non-sepele) adalah
eval
. Kemudian Anda dapat menulis perintah seperti yang Anda lakukan pada baris perintah dan dieksekusi persis seolah-olah Anda baru saja memasukkannya. Tetapi Anda harus mengutip semuanya.Kasus sederhana:
tidak begitu sederhana:
sumber
Tanda kutip kedua mematahkan perintah.
Ketika saya menjalankan:
Itu memberi saya kesalahan.
Tapi ketika saya lari
Tidak ada kesalahan sama sekali
Tidak ada cara untuk memperbaikinya pada saat itu (untuk saya) tetapi Anda dapat menghindari kesalahan dengan tidak memiliki ruang dalam nama direktori.
Jawaban ini mengatakan perintah eval dapat digunakan untuk memperbaikinya tetapi tidak berhasil untuk saya :(
sumber
Jika ini tidak berhasil
''
, maka Anda harus menggunakan``
:Perbarui cara yang lebih baik:
sumber
$( command... )
alih-alih backticks.Bagaimana dengan one-liner python3?
sumber