Dalam skrip Bash, saya mencoba untuk menyimpan opsi yang saya gunakan rsync
dalam variabel yang terpisah. Ini berfungsi dengan baik untuk opsi sederhana (seperti --recursive
), tapi saya mengalami masalah dengan --exclude='.*'
:
$ find source
source
source/.bar
source/foo
$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo
sent 57 bytes received 19 bytes 152.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
$ RSYNC_OPTIONS="-rnv --exclude='.*'"
$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo
sent 78 bytes received 22 bytes 200.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
Seperti yang Anda lihat, meneruskan --exclude='.*'
ke rsync
"secara manual" berfungsi dengan baik ( .bar
tidak disalin), itu tidak berfungsi ketika opsi disimpan dalam variabel terlebih dahulu.
Saya menduga ini terkait dengan tanda kutip atau wildcard (atau keduanya), tetapi saya belum dapat menemukan apa yang sebenarnya salah.
Jawaban:
Secara umum, itu ide yang buruk untuk menurunkan daftar item yang terpisah menjadi satu string, tidak peduli apakah itu daftar opsi baris perintah atau daftar nama path.
Alih-alih menggunakan array:
atau
dan kemudian ...
Dengan cara ini, kutipan dari opsi-opsi individual dipertahankan (selama Anda menggandakan penawaran ekspansi
${rsync_options[@]}
). Ini juga memungkinkan Anda untuk dengan mudah memanipulasi entri individual dari array, apakah Anda perlu melakukannya, sebelum memanggilrsync
.Dalam setiap shell POSIX, seseorang dapat menggunakan daftar parameter posisi untuk ini:
Sekali lagi, mengutip ganda ekspansi
$@
sangat penting di sini.Terkait secara tangensial:
Masalahnya adalah ketika Anda meletakkan dua set opsi ke dalam string, tanda kutip tunggal dari nilai
--exclude
opsi menjadi bagian dari nilai itu. Karenanya,akan bekerja¹ ... tetapi lebih baik (seperti dalam lebih aman) untuk menggunakan array atau parameter posisi dengan entri yang dikutip secara individual. Melakukan hal itu juga akan memungkinkan Anda untuk menggunakan hal-hal dengan spasi di dalamnya, jika Anda perlu, dan menghindari memiliki shell melakukan pembuatan nama file (globbing) pada opsi.
¹ asalkan
$IFS
tidak diubah dan bahwa tidak ada berkas yang dimulai nama dengan--exclude=.
di direktori saat ini, dan bahwanullglob
ataufailglob
shell pilihan tidak diatur.sumber
@ 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
):Melewati opsi "secara manual":
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 :
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: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
: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 :Ternyata Bash tidak melakukan ekspansi pola, tapi pola
--exclude=.*
tidak cocok file apapun, sehingga pola diteruskan kepada perintah. Membandingkan:Namun, tidak mengutip polanya berbahaya, karena jika (karena alasan apa pun) ada file yang cocok
--exclude=.*
maka polanya akan diperluas: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:
Saat meneruskan opsi ke perintah, kami menggunakan sintaks
"${ARRAY[@]}"
, yang memperluas setiap elemen array menjadi kata yang terpisah:sumber
Ketika kita menulis fungsi dan skrip shell, di mana argumen diteruskan untuk diproses, argumen akan dikirimkan ke variabel yang dinamai secara numerik, misalnya $ 1, $ 2, $ 3
Sebagai contoh :
bash my_script.sh Hello 42 World
Di dalam
my_script.sh
, perintah akan digunakan$1
untuk merujuk ke Halo,$2
untuk42
, dan$3
untukWorld
Referensi variabel
$0
,, akan diperluas ke nama skrip saat ini, misalnyamy_script.sh
Jangan mainkan seluruh kode dengan perintah sebagai variabel.
Ingat :
1 Hindari menggunakan nama variabel huruf besar semua dalam skrip.
2 Jangan gunakan backquotes, gunakan $ (...) sebagai gantinya, itu bersarang lebih baik.
sumber