Tırnak içine alınmış bir değişkendeki seçenekler neden başarısız olur, ancak alıntı yapılmadığında çalışır?


18

Ben bash, örneğin "$ foo" yerine $ foo değişkenleri alıntı gerektiğini okumuştum. Ancak, bir senaryo yazarken, tırnak işaretleri olmadan çalıştığı ancak onlarla çalışmadığı bir durum buldum:

wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line 
relative_path="$3" # /XXX received from command line

Bu işe yarıyor:

wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

Bu ($ wget_options ile ilgili çift tırnaklara dikkat edin):

wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
  • Bunun nedeni nedir?

  • İlk satır iyi sürüm; veya bu davranışa neden olan bir yerde gizli bir hata olduğundan şüphelenmeli miyim?

  • Genel olarak, bash ve alıntılarının nasıl çalıştığını anlamak için iyi belgeleri nerede bulabilirim? Bu senaryoyu yazarken, kuralları anlamak yerine deneme yanılma temelinde çalışmaya başladığımı hissediyorum.


3
Sorunuz burada cevaplanmıştır: mywiki.wooledge.org/BashFAQ/050
glenn jackman

3
Kurallar için kaynağa gidin: bash manual . Bölüm 3.5 "Kabuk Genişletmeleri", özellikle kelime bölme ve dosya adı genişletme konularına çok dikkat edin - bu 2 faktör kontrol etmek için tırnak kullandığınız şeydir.
glenn jackman


4
Bence komut satırı argümanlarının nasıl düşük seviyede çalıştığını anlamaya yardımcı olur. Bir program yürütüldüğünde, karakter listelerinin listesi olarak bağımsız değişkenler alır (yeterince yakın). Her iç liste "argüman" dediğimiz şeydir. Çoğu program, argümanlar arasındaki mantıksal ayrımlara bağlıdır. Burada, wgetbunun ne --mirror --no-host-directoriesanlama geldiğini bilmediğini görüyorsunuz (tek bir argüman olarak), ancak iki argümana ayrıldığında bunu ele alıyor . Çok az sayıda program, argüman vektörünün içine girdikten sonra boşlukları ve alıntıları özel olarak ele alır. Sorun şu ki bash, ve diğer mermiler şu şekilde olmalıdır:
HTNW

2
> insanlar tarafından kullanılır. Bağımsız değişkenler arasındaki sınırları manuel olarak tanımlamak can sıkıcı olacaktır, bu nedenle bir satırı (bir karakter listesi) bir bağımsız değişken vektörüne (karakter listelerinin bir listesi) dönüştürmek için boşluklara bölünür. Değişken genişleme, ilk genişlemelerin bashyaptığı bir şeydir, bu yüzden $abunun içeriğini doğrudan yazmakla tam olarak eşdeğer olduğunu hayal edebilirsiniz . Şimdi sorun ortada: a="-a -b"; cmd "$a"genişliyor cmd "-a -b", ama cmdmuhtemelen bunun ne anlama geldiğini bilmiyor. cmd $agenişler cmd -a -bmuhtemelen hangi yapar işi.
HTNW

Yanıtlar:


28

Temel olarak, değişken bölmelerini sözcük bölünmesinden (ve dosya adı oluşumundan) korumak için iki kez alıntı yapmanız gerekir. Ancak, örneğinizde,

wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

kelime bölme tam olarak istediğiniz şeydir .

İle "$wget_options"(alıntı), wgettek bir argümanla ne yapacağını bilmiyor --mirror --no-host-directoriesve şikayet ediyor

wget: unknown option -- mirror --no-host-directories

İçin wgetiki seçenek görmek --mirrorve --no-host-directoriesayrı, kelime bölme meydana zorundadır.

Bunu yapmanın daha sağlam yolları var. Eğer kullanıyorsanız bashveya kullanımları diziler gibi herhangi bir başka kabuk bashyapmak, bkz Glenn Jackman cevabı . Gilles'in yanıtı , standart gibi plainer mermileri için alternatif bir çözümü de açıklamaktadır /bin/sh. Her ikisi de esasen her seçeneği bir dizide ayrı bir öğe olarak saklar.

İyi cevaplarla ilgili soru: Kabuk betiğim neden boşlukta veya diğer özel karakterlerde boğuluyor?


Değişken açılımları iki katına çıkarmak iyi bir kural. Yap ţunu . O zaman bunu yapmamanız gereken birkaç durumun farkında olun. Bunlar, yukarıdaki hata mesajı gibi teşhis mesajları aracılığıyla size sunulacaktır.

Değişken açılımları belirtmeniz gerekmeyen birkaç durum da vardır . Ancak, çok fazla fark yaratmadığı için çift tırnak kullanmaya devam etmek daha kolaydır. Böyle bir durum

variable=$other_variable

Bir diğeri

case $variable in
    ...) ... ;;
esac

2
Bu split + glob operatörünü kullanmadan önce $IFS, doğru değeri içerdiğinden emin olunması gerekebilir . Burada boşlukta bölünmeniz gerekir ve metin herhangi bir sekme veya yeni satır içermez, bu nedenle varsayılan değeri $IFSbunu yapar, ancak bu kod $IFSdeğiştirilebilecek bir bağlamda çağrılabilecek bir işlevde kullanılacaksa , $IFSönceden ayarlamak istersiniz (ve muhtemelen daha sonra geri yükler veya kodun geri kalanında herhangi bir değişiklik yapılmazsa yerel bir kapsam kullanırsınız $IFS)
Stéphane Chazelas

32

Diziyi kullanmak için kodlamanın en sağlam yolu:

wget_options=(
    --mirror 
    --no-host-directories
    --directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"

Bu doğru cevap. Referans
l0b0

2
Bu iyi bir yanıt, bu yüzden iptal ettim ama Kusalanda kodumun neden yanlış olduğunu anlamam için bana daha fazla yardımcı oldu ve sadece birini kabul edebilirim.
z32a7ul

Rsync listesindeki biri bana bu yapıyı gösterene kadar bir sorun dünyasına giriyordum. Öğelerin bazılarının boş dizeler olması özellikle yararlıdır. Bu, boş dizelerin kaybolmasını sağlar. Bazı komutlar , komutunuz gibi bir şeye genişlerse beklenmedik şeyler yapar cpve rsyncyapar rsync '' rest of parameters. Bu, bir komut parçasını parça parça koşullu olarak oluşturmak ve daha sonra tek bir yerde çalıştırmak için mükemmeldir.
Joe

17

Bir dize değişkeninde bir dize listesi depolamaya çalışıyorsunuz. Uymuyor. Değişkene nasıl eriştiğiniz önemli değil, bir şeyler bozuldu.

wget_options='--mirror --no-host-directories'değişkeni wget_optionsboşluk içeren bir dizeye ayarlar . Bu noktada, alanın bir seçeneğin parçası mı yoksa seçenekler arasında bir ayırıcı mı olduğunu bilmenin bir yolu yoktur.

Bir değişken ikamesi ile değişkene eriştiğinizde, değişkenin wget "$wget_options"değeri bir dize olarak kullanılır. Bu, tek bir parametre olarak geçirildiği anlamına gelir, bu wgetyüzden tek bir seçenektir. Bu, sizin durumunuzda kırılıyor çünkü birden çok seçenek anlamına geliyordu.

Alıntılanmamış bir ikame kullandığınızda wget $wget_options, dize değişkeninin değeri “split + glob” olarak adlandırılan bir genişletme işleminden geçer:

  1. Değişkenin değerini alın ve boşlukla ayrılmış parçalara bölün ( $IFSdeğişkeni değiştirmediğiniz varsayılarak ). Bu, ara dizelerin bir listesi ile sonuçlanır.
  2. Ara listenin her öğesi için, bir veya daha fazla dosyayla eşleşen bir joker karakter deseni ise, bu öğeyi eşleşen dosyalar listesiyle değiştirin.

Bu, örneğinizde işe yarar, çünkü bölme işlemi alanı ayırıcıya dönüştürür, ancak bir seçenek boşluklar ve joker karakterler içerebileceğinden genel olarak çalışmaz.

Ksh, bash, yash ve zsh'de bir dizi değişkeni kullanabilirsiniz. Kabuk terminolojisindeki bir dizi, dizelerin listesidir, bu nedenle bilgi kaybı olmaz. Dizi değişkeni yapmak için, değişkene değer atarken parantezleri dizi öğelerinin etrafına yerleştirin. Dizinin tüm öğelerine erişmek için şunu kullanın: - bu, dizinin öğelerinden bir liste oluşturan genellemesidir . Burada da çift tırnaklara ihtiyacınız olduğunu unutmayın, aksi takdirde her eleman split + glob'dan geçer."${VARIABLE[@]}""$@"

wget_options=(--mirror --no-host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" 

Düz sh'de dizi değişkeni yoktur. Konumsal bağımsız değişkenleri kaybetmenin bir sakıncası yoksa, bunları bir dizgi listesini saklamak için kullanabilirsiniz.

set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" 

Daha fazla bilgi için bkz. Kabuk betiğim neden boşlukta veya diğer özel karakterlerde boğuluyor?


Düz sh için bir alt kabuk pozisyonel argümanlar koruyacak: (set -- ...; exec wget "$@" ...).
John Kugelman,
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.