@Kusalananda temel sorunu ve nasıl çözüleceğini zaten açıkladı ve @glenn jackmann ile bağlantılı Bash SSS girişi de birçok yararlı bilgi sağlıyor. İşte bu kaynaklara dayanarak sorunumda neler olduğuna dair ayrıntılı bir açıklama.
Olayları göstermek için argümanlarının her birini ayrı bir satıra yazan küçük bir komut dosyası kullanacağız ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Seçenekleri "manuel olarak" iletme:
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Beklendiği gibi, bölümler -rnv
ve --exclude='.*'
iki bağımsız değişkene ayrılırlar, çünkü bunlar alıntılanmamış boşlukla ayrılırlar (buna kelime ayırma denir ).
Ayrıca, tırnak işaretleri .*
kaldırıldığını unutmayın : tek tırnak işaretleri kabuğa içeriklerini özel yorum yapmadan geçirmelerini söyler , ancak tırnakların kendileri komuta iletilmez .
Şimdi seçenekleri bir değişkende dize olarak (bir dizi kullanmak yerine) saklarsak, tırnak işaretleri kaldırılmaz :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Bunun iki nedeni vardır: tanımlarken kullanılan çift tırnaklar $OPTS
tek tırnakların özel olarak işlenmesini önler, bu nedenle son tırnaklar değerin bir parçasıdır:
$ echo $OPTS
--exclude='.*'
Şimdi $OPTS
bir komuta argüman olarak kullandığımızda , tırnak işaretleri parametre genişletmeden önce işlenir , böylece tırnak işaretleri $OPTS
"çok geç" olur.
Bu, (orijinal sorunumda) rsync
desen '.*'
yerine hariç tutma desenini (tırnak işaretleriyle!) Kullandığımız anlamına gelir .*
- adı tek tırnaktan sonra bir nokta ile başlayan ve tek bir tırnakla biten dosyaları hariç tutar. Açıkçası amaçlanan bu değildi.
Geçici çözüm, tanımlarken çift tırnak işaretlerini atlamak olabilirdi $OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Bununla birlikte, daha karmaşık durumlarda küçük farklılıklar nedeniyle her zaman değişken atamaları teklif etmek iyi bir uygulamadır .
@Kusalananda'nın belirttiği gibi, alıntı yapmamak .*
da işe yarardı . Desen genişlemesini önlemek için tırnak işaretleri ekledim , ancak bu özel durumda bu kesinlikle gerekli değildi :
$ ./argtest.bash --exclude=.*
--exclude=.*
O Bash çıkıyor mu desen genişlemesini gerçekleştirmek, ancak desen --exclude=.*
herhangi bir dosyayı eşleşmiyor, bu nedenle desen komutuna geçer. Karşılaştırmak:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Ancak, kalıptan alıntı yapmak tehlikelidir, çünkü (hangi nedenle olursa olsun) eşleşen bir dosya --exclude=.*
varsa, kalıp genişletilir:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Son olarak, bir dizi kullanmanın neden alıntı sorunumu önlediğini görelim (komut argümanlarını depolamak için dizileri kullanmanın diğer avantajlarına ek olarak).
Diziyi tanımlarken, kelime ayırma ve alıntı işleme beklendiği gibi gerçekleşir:
$ 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=.*
Seçenekleri komuta iletirken "${ARRAY[@]}"
, dizinin her öğesini ayrı bir sözcüğe genişleten sözdizimini kullanırız :
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*