Komut seçenekleri için kabuk değişkenlerini kullanma


19

Bash betiğinde, kullandığım seçenekleri rsyncayrı bir değişkende depolamaya çalışıyorum . Bu basit seçenekler (gibi --recursive) için iyi çalışır , ancak ile ilgili sorunlar yaşıyorum --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)

Gördüğünüz gibi, geçen --exclude='.*'etmek rsync"elle" cezası (işleri .barkopyalanmaz) seçenekleri öncelikle bir değişkende saklanır zaman, çalışır değil.

Bunun tırnak işaretleri veya joker karakterle (veya her ikisiyle) ilgili olduğunu tahmin ediyorum, ancak tam olarak neyin yanlış olduğunu anlayamadım.



1
Veya bu yanıtı kendi sitemizde görebilirsiniz.
Scott

Yanıtlar:


38

Genel olarak, komut satırı seçeneklerinin bir listesi veya yol adları listesi olsun, ayrı bir öğe listesini tek bir dizeye indirgemek kötü bir fikirdir.

Bunun yerine bir dizi kullanma:

rsync_options=( -rnv --exclude='.*' )

veya

rsync_options=( -r -n -v --exclude='.*' )

ve sonra...

rsync "${rsync_options[@]}" source/ target

Bu şekilde, bireysel seçeneklerin alıntılanması korunur (genişletmeyi iki kez alıntıladığınız sürece ${rsync_options[@]}). Ayrıca, aramadan önce dizinin tek tek girişlerini kolayca değiştirmenize izin verir rsync.

Herhangi bir POSIX kabuğunda, bunun için konumsal parametrelerin listesi kullanılabilir:

set -- -rnv --exclude='.*'

rsync "$@" source/ target

Yine, genişlemesinin iki katından alıntı $@burada önemlidir.

Teğetsel olarak ilgili:


Sorun, iki seçenek kümesini bir dizeye koyduğunuzda, --excludeseçeneğin değerinin tek tırnaklarının bu değerin bir parçası haline gelmesidir. Bu nedenle,

RSYNC_OPTIONS='-rnv --exclude=.*'

işe yarayacaktır¹ ... ama ayrı ayrı belirtilen girişlerle bir dizi veya konumsal parametreler kullanmak daha iyidir (daha güvenli olduğu gibi). Bunu yapmak, içlerinde boşluk olan şeyleri kullanmanıza da izin verir ve kabuğun seçeneklerde dosya adı oluşturma (globbing) yapmasını önler.


¹ $IFSdeğiştirilmemiş ve adı --exclude=.geçerli dizinde başlayacak bir dosya olmaması ve nullglobveya failglobkabuk seçeneklerinin ayarlanmamış olması şartıyla .


Bir dizi kullanmak iyi çalışıyor, ayrıntılı cevabınız için teşekkür ederiz!
Florian Brucker

3

@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 -rnvve --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 $OPTStek tırnakların özel olarak işlenmesini önler, bu nedenle son tırnaklar değerin bir parçasıdır:

$ echo $OPTS
--exclude='.*'

Şimdi $OPTSbir 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) rsyncdesen '.*'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=.*

Bu şeyler beni uzun süre karıştırdı, bu yüzden bunun gibi ayrıntılı bir açıklama yararlı oldu.
Joe

0

İşlenecek bağımsız değişkenlerin geçirildiği işlevler ve kabuk komut dosyaları yazdığımızda, bağımsız değişkenler sayısal olarak adlandırılmış değişkenlere iletilir, örn. $ 1, $ 2, $ 3

Örneğin :

bash my_script.sh Hello 42 World

İçeride my_script.sh, komutlar $1Merhaba, $2için 42ve $3için kullanılırWorld

Başvuru değişkeni $0, geçerli komut dosyasının adına genişletilir, ör.my_script.sh

Kodun tamamını değişken olarak komutlarla oynatmayın.

Unutmayın :

1 Komut dosyalarında büyük harfli değişken adları kullanmaktan kaçının.

2 Geri tırnak kullanmayın, bunun yerine $ (...) kullanın, daha iyi yuva yapar.

if [ $# -ne 2 ]
then
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.