Bash / POSIX mermilerinde bir değişken vermeyi unutmanın güvenlik etkileri


206

Eğer unix.stackexchange.com’u bir süredir takip ediyorsanız echo $var, Bourne / POSIX mermilerinde liste içeriğinde (içinde olduğu gibi ) değişken olmayan bir değişkeni bırakmanın çok özel bir anlamı olduğunu ve umarım ki çok iyi bir nedeniniz olmadığı sürece yapılmamalı.

Q & burada A (Örnekler bir dizi uzun uzadıya tartışıldı oluyor: ? Boşluk veya diğer özel karakterler benim kabuk komut dosyası jikleyi yapar Neden , ? Ne zaman çift alıntı gereklidir , bir kabuk değişkeni ve üzerinde glob ve bölünmenin etkilerinin Genişleme , Alıntı vs tırnaksız dize genişletme)

Bourne kabuğunun ilk serbest bırakılmasından bu yana, 70'li yılların sonlarında ve Korn kabuğu tarafından değiştirilmedi ( David Korn'un en büyük pişmanlıklarından biri (soru # 7) ) ya bashda çoğunlukla Korn kabuğunu kopyaladı. Bunun POSIX / Unix tarafından nasıl belirtildiği.

Şimdi, burada hala bazı cevaplar görüyoruz ve değişkenlerin alıntılanmadığı yerlerde bazen halka açık bir kabuk kodu bile görüyoruz. İnsanların şimdiye kadar öğreneceklerini düşünürdün.

Tecrübelerime göre değişkenlerini alıntılamak zorunda olmayan başlıca 3 tip insan var:

  • başlayanlar. Bunlar kuşkusuz, tamamen sezgisel olmayan bir sözdizimi olduğu için mazeret görülebilir. Ve bu sitedeki onları eğitmek bizim rolümüz.

  • unutkan insanlar

  • Tekrarlanan çekiçlemeden sonra bile ikna olmamış insanlar, kesinlikle Bourne kabuğunun yazarının tüm değişkenlerimizi alıntılama niyetinde olmadıklarını düşünen insanlar .

Bu tür davranışlarla ilgili riski ortaya çıkarırsak belki onları ikna edebiliriz.

Değişkenlerinizden alıntı yapmayı unutursanız, olabilecek en kötü şey ne olabilir? Gerçekten o kadar kötü mü?

Ne tür bir güvenlik açığından bahsediyoruz?

Hangi bağlamlarda sorun olabilir?


8
BashPitfalls sanırım seveceğin bir şey.
pawel7318

Bu makaleden backlink yazdım , yazdığınız için teşekkürler
mirabilos

5
Dördüncü bir grubun eklenmesini önermek isterim: başını çok fazla defalarca aşırı alıntı yapmaktan etkilenen insanlar , belki de üçüncü grubun üyeleri tarafından başkaları hakkındaki hayal kırıklıklarını gidermek (kurban olmak). Tabii ki üzücü olan şey, dördüncü gruptakilerin en önemli şeyleri alıntılamada başarısız olmalarıdır.
ack

Yanıtlar:


201

önsöz

İlk önce, sorunu çözmenin doğru yolu olmadığını söyleyebilirim. “ İnsanları öldürmemelisin çünkü aksi halde hapse gireceksindemek gibi bir şey .

Benzer şekilde değişkeninizi de alıntılamıyorsunuz, aksi halde güvenlik açıklarını ortaya çıkarıyorsunuz. Değişkenlerinizden alıntı yapıyorsunuz çünkü yanlış değil (ama hapis korkusu yardımcı olabilirse neden olmasın).

Trende yeni atlayanlar için küçük bir özet.

Çoğu kabukta, değişken bir genleşmeyi işaretsiz bırakmak (buna rağmen (ve bu cevabın geri kalanı) komut değiştirme ( `...`veya $(...)) için de geçerlidir ve aritmetik genişleme ( $((...))veya $[...])) çok özel bir anlama sahiptir. Bunu tarif etmenin en iyi yolu, bir çeşit gizli split + glob operatörü¹ çağırmak gibidir .

cmd $var

başka bir dilde şöyle bir şey yazılmış olur:

cmd(glob(split($var)))

$varönce $IFSözel parametreyi ( bölünmüş kısım) içeren karmaşık kurallara göre bir sözcük listesine bölünür ve daha sonra bu bölünmeden kaynaklanan her sözcük , kendisiyle eşleşen dosyaların bir listesine genişletilen bir kalıp olarak değerlendirilir ( küre bölümü) .

Örnek olarak, $variçerir *.txt,/var/*.xmlve $IFS içerirse ,, cmdbirincisi cmdve diğeri txt mevcut dizindeki xmldosyalar ve içindeki dosyalar olmak üzere çeşitli argümanlarla çağrılır /var.

cmdSadece iki değişmez argümanla aramak istersen cmd ve şunu *.txt,/var/*.xmlyazarsın:

cmd "$var"

Diğer daha tanıdık bir dilde olurdu:

cmd($var)

Bir kabuktaki güvenlik açığı ile ne kastediyoruz ?

Ne de olsa, kabuk senaryolarının güvenliğe duyarlı bağlamlarda kullanılmaması gerektiğinden beri biliniyor. Elbette, tamam, değişkeni değişkensiz bırakmak bir hatadır ama bu çok fazla zarar veremez, değil mi?

Eh, kimsenin size kabuk komut dosyalarının hiçbir zaman web CGI'leri için kullanılması gerektiğini söylememesine rağmen ya da neyse ki çoğu sistem bugünlerde setuid / setgid kabuk komut dosyalarına izin vermez, kabukları tek bir şey (uzaktan sömürülebilir bash hatası yapan şey) Eylül 2014) 'de manşetler ortaya kabukları hala yaygın olarak kullanılmaktadır nerede olduğunu muhtemelen olmamalı: CGI'lerin, DHCP istemci kanca çağrılan sudoers komutları, içinde komut, tarafından (değilse olarak ) setuid komutlar ...

Bazen bilmeden. Örneğin system('cmd $PATH_INFO') , bir php/ perl/ pythonCGI betiğinde bu komut satırını yorumlamak için bir kabuk çağırır ( cmdkendisinin bir kabuk betiği olabileceği ve yazarı bir CGI'dan çağrılmasını hiç beklemeyebileceği gerçeğinden bahsetmez ).

Ayrıcalık yükselmesi için bir yol olduğunda, yani birisinin (onu saldırgan olarak adlandıralım ) onun amaçlanmadığı bir şeyi yapabileceği bir güvenlik açığı vardır .

Değişmez bir şekilde bu , veri sağlayan saldırganın , verilerin bir hata nedeniyle çoğu durumda yanlışlıkla yapmaması gereken bir şeyi yapan, ayrıcalıklı bir kullanıcı / işlem tarafından işlendiği anlamına gelir .

Temel olarak, buggy kodunuz saldırganın kontrolü altındaki verileri işlerken bir sorun yaşarsınız .

Şimdi, bu verilerin nereden gelebileceği her zaman açık değildir ve kodunuzun güvenilmeyen verileri işleme alıp alamayacağını anlamak genellikle zordur.

Değişkenler söz konusu olduğunda, bir CGI betiği söz konusu olduğunda, bu oldukça açık, veriler CGI GET / POST parametreleri ve çerezler, yol, host ... parametreleri gibi şeylerdir.

Bir setuid betiği için (bir başkası tarafından çağrıldığında bir kullanıcı olarak çalışan), bağımsız değişkenler veya ortam değişkenleridir.

Diğer bir çok yaygın vektör dosya isimleridir. Bir dizinden bir dosya listesi alıyorsanız, dosyaları saldırgan tarafından oraya yerleştirilmiş olabilir .

(Dosyaları işlerken bu bağlamda, hatta interaktif bir kabuk isteminde, savunmasız olabilir /tmpveya ~/tmp örneğin).

Hatta bir ~/.bashrcgüvenlik açığı olabilir (örneğin, müşterinin kontrolü altında bazı değişkenlerle sunucu dağıtımlarında benzerleri çalıştırmak için bashçağrıldığında yorumlayacaktır ).sshForcedCommandgit

Şimdi, bir komut dosyası güvenilir olmayan verileri işlemek için doğrudan çağrılmayabilir, ancak bunu yapan başka bir komutla çağrılabilir. Ya da yanlış kodunuz, kopyalayanlara (komut satırında veya iş arkadaşlarınızdan 3 yıl sonra) yazılan komut dosyalarına yapıştırılabilir. Özellikle kritik öneme sahip yerlerden biri, kodunuzun kopyalarının nerede biteceğini asla bilemeyeceğiniz için soru-cevap sitelerinde cevaplardır.

İş aşağı; ne kadar kötü

Bir değişkeni (veya komut değişikliğini) belirtmeden bırakmak, kabuk koduyla ilişkili bir numaralı güvenlik açıklarıdır. Kısmen bu hataların çoğu zaman güvenlik açıklarına dönüştüğü, ancak aynı zamanda işaretlenmemiş değişkenleri görmek çok yaygın olduğu için.

Aslında, kabuk kodundaki güvenlik açıklarını ararken, yapılacak ilk şey işaretsiz değişkenleri aramaktır. Genellikle iyi bir aday bulmak, genellikle saldırgan tarafından kontrol edilen verilere ulaşmak kolaydır.

Alıntı yapılmayan bir değişkenin güvenlik açığına dönüşmesinin sonsuz sayıda yolu vardır. Burada sadece birkaç ortak eğilim vereceğim.

Bilgi açıklaması

Çoğu kişi, ayrık bölüm nedeniyle (örneğin, günümüzde adlarında boşluk olması ve alanın IFS'nin varsayılan değerinde olması yaygındır), değişken olmayan değişkenlerle ilişkili hatalarla karşılaşacaktır . Birçok insan glob bölümünü görmezden gelecek . Glob parçası olarak en az tehlikelidir bölünmüş parçası.

Sterilize edilmemiş harici giriş üzerine yapılan küreleme , saldırganın herhangi bir dizinin içeriğini okumanızı sağladığı anlamına gelir .

İçinde:

echo You entered: $unsanitised_external_input

eğer $unsanitised_external_inputiçeriyor /*, yani saldırgan içeriğini görebilirsiniz /. Önemli değil. Gerçi daha ilginç hale ile /home/*, hangi makinede size kullanıcı adlarının bir listesini verir /tmp/*, /home/*/.forwarddiğer tehlikeli uygulamalara ipuçları için /etc/rc*/*etkin hizmetler için ... Gerek tek tek isim. Değeri /* /*/* /*/*/*...sadece tüm dosya sistemini listeleyecektir.

Hizmet reddi güvenlik açıkları.

Önceki davayı biraz ileri götürdüğümüzde bir DoS var.

Aslında, liste içeriğindeki, onaylanmamış girdi içeren herhangi bir değişken olmayan değişken, en azından bir DoS güvenlik açığıdır.

Uzman kabuk senaryoları bile genellikle şu şeyleri alıntılamayı unutur:

#! /bin/sh -
: ${QUERYSTRING=$1}

:no-op komutudur. Ne yanlış gidebilir ki?

Yani atamak için yaratılmış $1için $QUERYSTRINGeğer $QUERYSTRING tanımsız oldu. Bu, CGI betiğini komut satırından da çağrılabilir hale getirmenin hızlı bir yoludur.

Bu $QUERYSTRINGhala genişlemiş ve alıntı olmadığından split + glob operatörü çağrılıyor.

Şimdi, özellikle büyümesi pahalı olan bazı küreler var. /*/*/*/*Aşağı 4 seviyelerine kadar listeleme dizinleri demektir gibi bir yeterince kötü. Diske ve CPU aktivitesine ek olarak, bu on binlerce dosya yolunun depolanması anlamına gelir (burada minimum sunucu VM'de 40k, 10k hangi dizinlerde).

Şimdi /*/*/*/*/../../../../*/*/*/*40k x 10k anlamına geliyor ve /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*en güçlü makineyi bile dizlerine getirmek için yeterli.

Kendiniz deneyin (makinenizin çarpması veya takılması için hazırlıklı olsanız da):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

Tabii ki, eğer kod:

echo $QUERYSTRING > /some/file

Sonra diski doldurabilirsiniz.

Sadece kabuk cgi veya bash cgi veya ksh cgi üzerinde bir google araması yapın. CGI'lerin kabuklara nasıl yazılacağını gösteren birkaç sayfa bulacaksınız. Parametreleri işleyenlerin yarısının korunmasız olduğuna dikkat edin.

David Korn'un bile kendi savunmasızları (çerez kullanımına bakın).

rastgele kod yürütme güvenlik açıklarına kadar

Rasgele kod yürütme, en kötü güvenlik açığı türüdür, çünkü saldırgan herhangi bir komutu çalıştırabilirse, yapabileceklerinin sınırı yoktur.

Bu genellikle onlara götüren bölünmüş kısımdır. Bu bölme işlemi, yalnızca bir tane beklendiğinde komutlara iletilmesi gereken çeşitli argümanlarla sonuçlanır. Bunlardan ilki beklenen bağlamda kullanılacak olsa da, diğerleri farklı bir bağlamda olacak ve potansiyel olarak farklı şekilde yorumlanacaktır. Bir örnek ile daha iyi:

awk -v foo=$external_input '$2 == foo'

Burada amaç, $external_inputkabuk değişkeninin içeriğini değişkene atamaktı foo awk.

Şimdi:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

Ayrılmasından kaynaklanan ikinci sözcük $external_input , atanmamış fooancak awkkod olarak kabul edilmiştir (burada isteğe bağlı bir komut yürüten:) uname.

Bu da diğer komutları yürütebilirsiniz komutlar için özellikle bir problem ( awk, env, sed(GNU bir), perl, find...) özellikle (argümanlar sonra seçenekleri kabul) GNU biçimlerine sahiptir. Bazen, gibileri yürütmek edebilmek için komutlar şüpheli olmaz ksh, bashya zsh's [ya printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

Eğer bir dizin yaratırsak x -o yes, test pozitif olur, çünkü değerlendirdiğimiz tamamen farklı bir koşullu ifadedir.

Daha da kötüsü, x -a a[0$(uname>&2)] -gt 1en azından tüm ksh uygulamalarıyla ( sh çoğu ticari Unice ve bazı BSD'leri içerir) adında bir dosya oluşturursak , uname bu kabukları [komutun sayısal karşılaştırma işleçlerinde aritmetik değerlendirme gerçekleştirdiğinden çalıştırır .

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

bashGibi bir dosya adı için aynı x -a -v a[0$(uname>&2)].

Tabii ki, keyfi bir şekilde yürütülemiyorlarsa, saldırgan daha az hasar alabilir (bu, keyfi bir şekilde yürütmeye yardımcı olabilir). Dosya yazabilen veya izinleri değiştirebilen, sahiplik yapan veya herhangi bir ana veya yan etkiye sahip olan herhangi bir komut kullanılabilir.

Dosya adlarıyla her türlü şey yapılabilir.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

Ve sen sonunda ..yazı yazabilirsin (özyinelemeli GNU ile chmod).

Halka açık alanlardaki dosyaların otomatik olarak işlenmesini sağlayan komut dosyaları /tmpçok dikkatli bir şekilde yazılmalıdır.

Ne dersin [ $# -gt 1 ]

Bu yorucu buluyorum bir şey. Bazı insanlar, belirli bir genişlemenin, teklifleri göz ardı edip etmeyeceklerine karar vermede sorunlu olup olmayacağını merak etme derdinden kurtulur.

Söylemek gibi. Hey, $#split + glob operatörüne tabi olamaz gibi görünüyor , hadi kabuğundan split + glob yapmasını isteyelim . Veya Hey, hatayı yanlış kodlayalım çünkü hatayı vurmak pek mümkün değil .

Şimdi bu ne kadar mümkün değil? Tamam, $#(veya $!, $?veya herhangi bir aritmetik ikame) sadece rakamları (veya -bazıları için) içerebilir, böylece glob kısmı dışarıda kalır . İçin bölünmüş bir parçası olsa bir şeyler yapmak için tek ihtiyacımız olan $IFSrakam içermeyen (veya -).

Bazı mermilerle $IFSçevreden miras alınabilir, ancak ortam güvenli değilse, yine de oyun biter.

Şimdi şöyle bir işlev yazarsanız:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

Bunun anlamı, işlevinizin davranışının, çağrıldığı içeriğe bağlı olmasıdır. Veya başka bir deyişle, $IFS girdilerden biri haline gelir. Açıkçası, işlevinize ilişkin API belgelerini yazdığınızda, şöyle bir şey olmalı:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

Ve fonksiyonunuzu çağıran kodun $IFSrakam içermediğinden emin olması gerekir . Tüm bunlar, bu 2 çift alıntı karakterini yazmaktan hoşlanmadığınız için.

Şimdi, bu [ $# -eq 2 ]hatanın bir güvenlik açığı olması için , saldırganın$IFS kontrolünde olması için bir şekilde değere ihtiyacınız olacak . Muhtemelen, saldırgan başka bir hatadan yararlanmayı başaramazsa normalde olmazdı .

Bu hiç duyulmamış değil. Ortak bir durum, insanların aritmetik ifadede kullanmadan önce verileri temizlemeyi unuttukları durumdur. Yukarıda, bazı mermilerde rastgele kod yürütülmesine izin verebileceğini gördük, ancak hepsinde , saldırganın herhangi bir değişkene bir tamsayı değeri vermesini sağlar .

Örneğin:

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

Ve bir $1değere sahip olarak (IFS=-1234567890), bu aritmetik değerlendirme, IFS ayarlarının yan etkisine sahiptir ve bir sonraki [ komut başarısız olur, bu da çok fazla arızanın kontrolünün atlandığı anlamına gelir .

Ne zaman hakkında bölünmüş + glob operatörü çağrılan değil?

Değişkenler ve diğer açılımlar etrafında alıntı yapılması gereken başka bir durum daha var: desen olarak kullanıldığında.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

olmadığını test yoktur $ave $b(haricinde aynıdır zsh) ama eğer $adesenle eşleşirse $b. Ve alıntı gerekir $bEğer dizeleri olarak karşılaştırmak istiyorsanız (aynı şey içinde "${a#$b}"ya "${a%$b}"ya "${a##*$b*}"nerede $bo değil bir desen olarak alınacak olursa alıntı olmalıdır).

Bunun anlamı olmasıdır [[ $a = $b ]]durumlarda doğru döndürebilir $afarklıdır $b(örneğin, zaman $aolduğu anythingve $bolduğu *) ya da aynı olduğunda (her iki örneği için yanlış döndürebilir $ave $bvardır [a]).

Bu bir güvenlik açığına neden olabilir mi? Evet, herhangi bir böcek gibi. Burada, saldırgan betiğinizin mantıksal kod akışını değiştirebilir ve / veya betiğinizin yaptığı varsayımları ihlal edebilir. Örneğin, şöyle bir kodla:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

Saldırgan , pas geçerek kontrolü atlayabilir '[a]' '[a]'.

Şimdi, ne eşleşen desen ne de split + glob operatörü geçerli değilse , bir değişkeni işaretsiz bırakma tehlikesi nedir?

Yazdığımı itiraf etmeliyim:

a=$b
case $a in...

Burada alıntı yapmak zarar vermez, ancak kesinlikle gerekli değildir.

Bununla birlikte, bu durumlarda teklif vermemenin bir yan etkisi (örneğin Q&A cevaplarında) yeni başlayanlara yanlış bir mesaj gönderebilmesidir: değişkenleri alıntılamamanın tamam olabileceği .

Örneğin, eğer a=$btamamsa, o export a=$bzaman da iyi olacağını düşünmeye başlayabilirler (ki bu, liste bağlamında komutun argümanlarında olduğu gibi birçok kabukta değildirexport ) veya env a=$b.

Ne hakkında zsh?

zshBu tasarım garipliklerinin çoğunu düzeltti. In zsh(en azından sh / Ksh benzetim modunda iken) İstersen, bölme veya globbing veya desen eşleştirme , siz özellikle istemek zorunda: $=varbölmek ve $~varglob veya değişkenin içeriği için olduğu gibi tedavi edilecek Bir kalıp

Bununla birlikte, bölünme (ancak topaklanma) hala kote edilmemiş komut değiştirme (dolaylı olarak) ile örtük olarak yapılır echo $(cmd).

Ayrıca, değişkenlerden alıntı yapmamanın bazen istenmeyen bir yan etkisi boşalmaları gidermektir . zshDavranış (ile tamamen globbing devre dışı bırakarak diğer kabuklarda neler başarabileceğini benzer set -f) ve bölme (ile IFS=''). Hala içinde:

cmd $var

Split + glob olmayacak , ancak $varboşsa, boş bir argüman almak yerine, cmdhiçbir argüman almayacaksınız.

Bu, böceklere (bariz gibi [ -n $var ]) neden olabilir . Bu muhtemelen bir betiğin beklentilerini ve varsayımlarını bozabilir ve güvenlik açıklarına yol açabilir, ancak şu anda çok fazla zorlanamayan bir örnek bulamıyorum).

Ne zaman hakkında ne gerek bölünmüş + glob operatörünü?

Evet, bu genellikle değişkeninizi alıntılanmamış bırakmak istersiniz. Ancak, split ve glob operatörlerinizi kullanmadan önce doğru şekilde ayarladığınızdan emin olmanız gerekir. Eğer bölünmüş parçayı sadece küre parçasını değil (çoğu zaman olduğu gibi) istiyorsanız, globbing'i ( set -o noglob/ set -f) devre dışı bırakmanız ve düzeltmeniz gerekir $IFS. Aksi halde, güvenlik açıklarına neden olursunuz (yukarıda belirtilen David Korn'ın CGI örneği gibi).

Sonuç

Kısacası, kabuklarda işaretsiz bir değişkenin (ya da komut ikame veya aritmetik genişlemenin) bırakılması, özellikle yanlış bağlamlarda yapıldığında gerçekten tehlikeli olabilir ve bu yanlış bağlamların hangileri olduğunu bilmek çok zor olabilir.

Kötü uygulama olarak kabul edilmesinin sebeplerinden biri de budur .

Şimdiye kadar okuduğunuz için teşekkürler. Başının üzerinden geçerse, endişelenme. Herkesin, kodlarını yazdıkları şekilde yazmanın tüm etkilerini anlamalarını bekleyemezsiniz. Bu yüzden iyi uygulama önerilerimiz var , bu yüzden mutlaka nedenini anlamadan takip edilebilirler.

(ve henüz kesin olarak açık değilse, lütfen güvenlik açısından hassas kodu kabuklara yazmaktan kaçının).

Ve lütfen bu sitedeki cevaplarınıza değişkenlerinizden alıntı yapın!


And ksh93ve pdkshve türevleri, topaklanma devre dışı bırakılmadıkça ayraç genişlemesi de gerçekleştirilir ( seçenek, devre dışı bırakılmış ksh93olsa bile ksh93u + sürümlerinde braceexpand).


Bununla birlikte [[, yalnızca karşılaştırmaların if [[ $1 = "$2" ]]; then
RHS'sinin

2
@mirabilos, evet, ama LHS'nin alıntılanmasına gerek yok, bu yüzden orada alıntı yapmamak için zorlayıcı bir neden yok (eğer yapılacak en mantıklı şey olduğu gibi varsayılan olarak alıntı yapmak için bilinçli bir karar alırsak) ). Ayrıca unutmayınız [[ $* = "$var" ]]aynı değildir [[ "$*" = "$var" ]]ilk karakteri ise $IFSuzay değildir bash(ve ayrıca mksheğer $IFSben emin değilim bu durumda olsa boş $*eşittir, ben bir hata yükseltmek gerekir?)).
Stéphane Chazelas

1
Evet, burada varsayılan olarak teklif verebilirsiniz. Lütfen şu anda alan bölme ile ilgili hiçbir hata yok, bunu tekrar değerlendirebilmemiz için, öncelikle (sizden ve diğerlerinden) bildiklerimi düzeltmem gerekiyor.
mirabilos

2
@Barmar, demek istediğinizi varsayalım, foo='bar; rm *'hayır demez, ancak bir bilginin ifşası olarak sayılabilecek mevcut dizinin içeriğini listeler. print $fooiçinde ksh93( printbunun yerine echobazı eksiklikleri giderir foo='-f%.0d z[0$(uname>&2)]') ( örneğin ile ) (aslında ihtiyacınız olan print -r -- "$foo". echo "$foo"hala yanlış ve düzeltilemez (genellikle daha az zararlıdır)) olsa da bir kod enjeksiyon güvenlik açığı vardır .
Stéphane Chazelas,

3
Ben bir bash uzmanı değilim, ancak on yıldan beri kodlama yapıyorum. Çok fazla tırnak kullandım, ancak daha çok gömülü boşlukları işlemek için kullandım. Şimdi onları daha çok kullanacağım! Tüm ince noktaları emmeyi biraz daha kolaylaştırmak için birisinin bu cevabı genişletmesi iyi olurdu. Çok şey aldım, ama ben de çok özledim. Zaten uzun bir yazı, ama öğrenmem için burada daha çok şey olduğunu biliyorum. Teşekkürler!
Joe,

34

[Esinlenerek bu cevap ile cas .]

Ama ya ...?

Fakat senaryomu kullanmadan önce bilinen bir değere bir değişken koyarsa ne olur? Özellikle, bir değişkeni iki veya daha fazla olası değerden birine ayarlarsa (ancak her zaman bilinen bir şeye ayarlarsa) ve değerlerden hiçbiri boşluk veya küre karakterleri içermiyorsa? Bu durumda alıntı olmadan kullanmak güvenli değil mi?

Peki ya olası değerlerden biri boş dize ise ve ben “boşaltma” ya bağlıyım? Yani, değişken boş dizeyi içeriyorsa, komutumda boş dizeyi almak istemiyorum; Hiçbir şey almak istemiyorum. Örneğin,

eğer bazı terimler
sonra
    ignorecase = "- i"
Başka
    ignorecase = ""
fi
                                        # Yukarıdaki komutlardaki alıntıların kesinlikle gerekli olmadığına dikkat edin . 
grep $ ignorecase   other_ grep _args

Söyleyemem ; Boş dize ise başarısız olur .grep "$ignorecase" other_grep_args$ignorecase

Tepki:

Diğer cevapta tartışıldığı gibi IFS, a -veya a içeriyorsa , bu yine de başarısız olacaktır i. Eğer sağlanmalıdır varsa IFSDeğişkeniniz herhangi bir karakter içermiyor (ve sizin değişken herhangi glob karakter içermediğinden emin), sonra bu muhtemelen güvenlidir.

Ancak daha güvenli bir yol var (biraz çirkin ve oldukça sezgisel olmasa da): kullanın ${ignorecase:+"$ignorecase"}. Gönderen POSIX Kabuk Komut Dili şartname altında  2.6.2 Parametre Genişleme ,

${parameter:+[word]}

    Alternatif Değer Kullan.   Eğer parameterayarlanmadan ya da boş, boş ikame edilir; Aksi takdirde, word (veya wordatlanırsa boş bir dizgenin ) genişlemesi ikame edilecektir.

Buradaki püf noktası, olduğu ignorecasegibi, parameter ve "$ignorecase"olarak kullandığımızdır word. Yani ${ignorecase:+"$ignorecase"}demek

Eğer $ignorecasebelirsiz veya boş (yani boş) ise boş (yani, alıntılanmamış hiçbir şey ) değiştirilmelidir; Aksi takdirde, genişlemesi "$ignorecase"ikame edilecektir.

Bu, bizi nereye gitmek istediğimize götürür: değişken boş dizgeye ayarlanmışsa “kaldırılır” (bu bütün, kıvrımlı ifade hiçbir şey olarak değerlendirir - boş bir dize bile olmaz) ve değişken olmayan bir - boş değer, bu değeri aldık.


Ama ya ...?

Peki ya kelimelere bölünmesini istediğim / ihtiyaç duyduğum bir değişken varsa? (Bu ilk durumda olduğu gibi; betiğim değişkeni ayarladı ve hiçbir glob karakteri içermediğinden eminim. sınırları.
PS hala boşaltma yapmak istiyorum.)

Örneğin,

eğer bazı terimler
sonra
    crit = "- f yazın"
Başka
    kriterleri = ""
fi
eğer bazı_otemler_condition
sonra
    ölçüt = "$ ölçüt - zaman +42"
fi
"$ start_directory" $ kriterlerini bulmak   other_ bulmak _args

Tepki:

Bunun kullanmak için bir dava olduğunu düşünebilirsiniz eval.  Hayır! evalBurada   kullanmayı düşünmek için bile cazibeye karşı durun .

Yine, IFSdeğişkeninizde herhangi bir karakter içermediğinden emin olduysanız (onurlandırmak istediğiniz boşluklar hariç) ve değişkeninizin herhangi bir glob karakteri içermediğinden eminseniz, muhtemelen kasa.

Ancak, bash (veya ksh, zsh veya yash) kullanıyorsanız, daha güvenli bir yol var: bir dizi kullanın:

eğer bazı terimler
sonra
    crit = (- type f) # `crit = (" - "" f "yazın) diyebilirsiniz, ancak gerçekten de gereksizdir.
Başka
    kriterler = () # etmeyin bu komutla üzerinde herhangi tırnak kullanın!
fi
eğer bazı_otemler_condition
sonra
    ölçütler + = (- mtime +42) # Not: bir diziye eklemek (eklemek) için `=` değil, ` + =`.
fi
"$ start_directory" "$ bulmak {kriterleri [@]}"   other_ bulmak _args

Kaynaktan Bash (1) ,

Bir dizinin herhangi bir elemanı kullanılarak referans alınabilir . Eğer ... ise veya  kelime tüm üyelerine genişler . Bu abonelikler yalnızca kelime çift tırnak işareti içinde göründüğünde farklılık gösterir. Eğer kelime çift alıntı ise,… her elemanını ayrı bir kelimeye genişletir .${name[subscript]}subscript@*name${name[@]}name

Böylece "${criteria[@]}"(yukarıdaki örnekte) criteria, her birinin alıntı yaptığı dizinin sıfır, iki veya dört öğesine genişletilir . Hiçbiri eğer Özellikle, koşul  s doğrudur, criteria(tarafından ayarlanan dizi içeriği yok criteria=()deyimi) ve "${criteria[@]}"değerlendirilen hiçbir şey (hatta uygunsuz bir boş dize).


Bu, bazıları önceden bilmediğiniz ve boşluk (lar) veya diğer özel karakter (ler) içerebilir, bazıları dinamik (kullanıcı) giriş olan birden fazla kelimeyle uğraşırken özellikle ilginç ve karmaşık bir hal alır. Düşünmek:

printf "Aranacak dosya adını girin:"
fname oku
eğer ["$ fname"! = ""]
sonra
    ölçüt + = (- "$ fname" adı)
fi

Not $fnamealıntılanmıştır her kullanıldığı zaman. Bu, kullanıcı foo barveya benzeri bir şey girse bile çalışır foo*;  veya olarak "${criteria[@]}"değerlendirir . (Dizinin her öğesinin alıntılandığını unutmayın.)-name "foo bar"-name "foo*"

Diziler tüm POSIX mermilerinde çalışmıyor; diziler bir ksh / bash / zsh / yash-ism'dir. Dışında… tüm mermilerin desteklediği bir dizi var: argüman listesi, aka "$@". Çalıştığınız argüman listesi bittiğinde (örneğin, tüm “konumsal parametreleri” (argümanlar) değişkenlere kopyaladınız veya başka şekilde işlediniz), argüman listesini bir dizi olarak kullanabilirsiniz:

eğer bazı terimler
sonra
    set - -type f # `set -" -type "" f "` diyebilirsiniz, ancak bu gerçekten gereksiz.
Başka
    Ayarlamak --
fi
eğer bazı_otemler_condition
sonra
    set - "$ @" -mtime +42
fi
# Benzer şekilde: set - "$ @" -name "$ fname"
"$ start_directory" "$ @"   ifadesini bulur other_ find _args

"$@"Yapı (ki, tarihsel olarak, ilk geldi) aynı anlambilim içeriyor - her argüman genişler (yani argüman listesinin her öğe) Yazdığınız sanki ayrı bir kelimeye, ."${name[@]}""$1" "$2" "$3" …

2.5.2 Özel Parametreler altındaki POSIX Kabuk Komut Dili spesifikasyonundan alıntı ,

@

    Konumsal parametrelere genişler, başlangıçtan ayarlanan her konum parametresi için bir alan oluşturur. …, İlk alanlar ayrı alanlar olarak korunacaktır,…. Konumsal parametre yoksa, genişlemesi çift ​​tırnak içinde @olsa bile sıfır alan oluşturur @; ...

Tam metin biraz şifreli; kilit nokta, "$@"konumsal parametre olmadığında sıfır alan üreteceğini belirtmesidir . Tarihsel not: "$@"ilk kez 1979'da Bourne kabuğuna (bash selefinden önce) "$@"girdiğinde, konumsal parametreler olmadığında tek bir boş dize ile değiştirilen bir böcek vardı ; bkz. Kabuk komut dosyasında ne anlama ${1+"$@"}geliyor ve bu fark "$@"nedir? Geleneksel Bourne Kabuğu AilesiNe anlama ${1+"$@"}geliyor ... ve nerede gerekli? ve "$@"karşı${1+"$@"} .


Diziler de ilk duruma yardımcı olur:

eğer bazı terimler
sonra
    ignorecase = (- i) # `ignorecase = (" - i ")` diyebilirsiniz, ancak gerçekten gereksizdir.
Başka
    ignorecase = () # etmeyin bu komutla üzerinde herhangi tırnak kullanın!
fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

Bu söylemeden geçmeli, ancak burada yeni olan kişilerin yararına: csh, tcsh, vb. Bourne / POSIX mermileri değildir. Onlar tamamen farklı bir aile. Farklı renkteki bir at. Tamamen başka bir top oyunu. Farklı bir kedi cinsi. Başka bir tüyün kuşları. Ve en önemlisi, farklı solucanlar olabilir.

Bu sayfada söylenenlerin bazıları csh için geçerlidir; Mesela: Yapmamak için iyi bir nedeniniz yoksa tüm değişkenlerinizi alıntılamak iyi bir fikirdir ve ne yaptığınızı bildiğinizden emin olabilirsiniz. Ancak, csh'da her değişken bir dizidir - bu hemen hemen her değişkenin sadece bir elementin dizisi olduğu ve Bourne / POSIX kabuklarındaki sıradan bir kabuk değişkenine oldukça benzer davrandığı anlamına gelir. Ve sözdizimi çok farklı (ve çok kötü demek istiyorum ). Yani burada csh-aile kabukları hakkında daha fazla bir şey söylemeyeceğiz.


1
Ki Not cshBunun yerine kullanmak istiyorum, $var:qdaha "$var"sonraki yeni satır karakterleri içeren değişkenler için çalışmıyor (veya tek tek dizinin elemanlarını alıntı yerine tek bir argüman boşluklarla onlara katılmak istiyorsanız).
Stéphane Chazelas,

Kısacası , komutla çalışmadığı için bitrate="--bitrate 320"çalışır ${bitrate:+$bitrate}ve bitrate=--bitrate 128çalışmaz ${bitrate:+"$bitrate"}. Is kullanmak güvenlidir ${variable:+$variable}hiçbir ile "?
Freedo

@Freedo: Yorumunuzu belirsiz buluyorum. Özellikle daha önce söylemediğim şeyleri bilmek istediklerinizi bilmiyorum. İkinci “Peki ya eğer” başlığını görün - “Peki ya kelimelere bölünmesini istediğim / ihtiyaç duyduğum bir değişkenim varsa?” Bu sizin durumunuz ve oradaki tavsiyeye uymalısınız. Fakat yapamıyorsanız (örneğin, bash, ksh, zsh veya yash dışında bir kabuk kullanıyorsanız $@ve başka bir şey kullanıyorsanız  ) veya başka nedenlerle bir dizi kullanmayı reddediyorsanız, bakın. “Diğer cevapta tartışıldığı gibi”. … (Devamı)
G-Man

(Devamı) ... Öneriniz (kullanarak ${variable:+$variable}hiçbir ile ") IFS içeriyorsa başarısız olur  -,  0, 2, 3, a, b, e, i, rveya  t.
G-Man

Şey, değişkenim hem a, e, b, i 2 ve 3 karakter hem de hala iyi çalışıyor${bitrate:+$bitrate}
Freedo

11

Stéphane'nin cevabından şüpheliydim, ancak kötüye kullanılması mümkün $#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

veya $ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

Bunlar tartışmalı örneklerdir, ancak potansiyel mevcuttur.

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.