@ Kusalananda telah menjelaskan masalah dasar dan bagaimana menyelesaikannya, dan entri Bash FAQ yang ditautkan oleh @glenn jackmann juga menyediakan banyak informasi berguna. Berikut adalah penjelasan terperinci tentang apa yang terjadi dalam masalah saya berdasarkan sumber daya ini.
Kami akan menggunakan skrip kecil yang mencetak setiap argumennya pada baris terpisah untuk menggambarkan hal-hal ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Melewati opsi "secara manual":
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Seperti yang diharapkan, bagian-bagian -rnv
dan --exclude='.*'
dibagi menjadi dua argumen, karena mereka dipisahkan oleh spasi kosong (ini disebut pemisahan kata ).
Juga perhatikan bahwa tanda kutip di sekitar .*
telah dihapus: tanda kutip tunggal memberitahu shell untuk meneruskan konten mereka tanpa interpretasi khusus , tetapi tanda kutip itu sendiri tidak diteruskan ke perintah .
Jika sekarang kita menyimpan opsi dalam variabel sebagai string (sebagai lawan menggunakan array), maka tanda kutip tidak dihapus :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Ini karena dua alasan: tanda kutip ganda yang digunakan ketika mendefinisikan $OPTS
mencegah perlakuan khusus dari tanda kutip tunggal, sehingga tanda kutip adalah bagian dari nilai:
$ echo $OPTS
--exclude='.*'
Ketika kita sekarang menggunakan $OPTS
argumen pada perintah, maka kutipan diproses sebelum ekspansi parameter , sehingga tanda kutip yang $OPTS
terjadi "terlambat".
Ini berarti bahwa (dalam masalah awal saya) rsync
menggunakan pola kecualikan '.*'
(dengan tanda kutip!) Alih-alih pola .*
- itu tidak termasuk file yang namanya dimulai dengan tanda kutip tunggal diikuti oleh titik dan berakhir dengan tanda kutip tunggal. Jelas bukan itu yang dimaksudkan.
Solusinya adalah dengan menghilangkan tanda kutip ganda ketika mendefinisikan $OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Namun, itu praktik yang baik untuk selalu mengutip penugasan variabel karena perbedaan halus dalam kasus yang lebih kompleks.
Seperti @Kusalananda catat, tidak mengutip .*
juga akan berhasil. Saya telah menambahkan tanda kutip untuk mencegah perluasan pola , tetapi itu tidak terlalu diperlukan dalam kasus khusus ini :
$ ./argtest.bash --exclude=.*
--exclude=.*
Ternyata Bash tidak melakukan ekspansi pola, tapi pola --exclude=.*
tidak cocok file apapun, sehingga pola diteruskan kepada perintah. Membandingkan:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Namun, tidak mengutip polanya berbahaya, karena jika (karena alasan apa pun) ada file yang cocok --exclude=.*
maka polanya akan diperluas:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Akhirnya, mari kita lihat mengapa menggunakan array mencegah masalah mengutip saya (di samping keuntungan lain menggunakan array untuk menyimpan argumen perintah).
Saat mendefinisikan array, pemisahan kata dan penanganan penawaran terjadi seperti yang diharapkan:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
Saat meneruskan opsi ke perintah, kami menggunakan sintaks "${ARRAY[@]}"
, yang memperluas setiap elemen array menjadi kata yang terpisah:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*