test, [ ve [[ (2020)
(jmmv.dev)- Unix benzeri sistemlerde adı tek karakterlik bir sembol olan
/bin/[çalıştırılabilir dosyası bulunabilir; kabuk koşulu gibi görünen söz dizimi de aslında komut çalıştırma ve çıkış kodları üzerine kuruludur test, ifadeyi değerlendirir; doğruysa 0, yanlışsa 1 döndürür ve[olarak çağrıldığında son argümanın]olup olmadığını da kontrol eder- Birçok kabuk
testve[komutlarını yerleşik komut olarak da sunduğundan, harici/bin/testile kabuğun yerleşik uygulamasının hata mesajları veya davranışları farklı olabilir - Bash uzantısı olan
[[, harici bir komut değil yerleşik bir söz dizimidir; bu yüzden örnekteki gibilong*değerini glob genişletmesine sokmadan literal dize olarak karşılaştırmak gibi[den farklı kurallar uygular - Taşınabilir betikler için
[uygundur; yalnızca Bash’e özel betiklerde ise[[yi tutarlı biçimde kullanmak daha iyidir, ancak iki yöntemin genişletme kuralları arasındaki farkı bilerek seçim yapmak gerekir
/bin/[ ve /bin/test nedir?
- Unix sistemlerinde adı tek karakterlik bir sembol olan
/bin/[çalıştırılabilir dosyası bulunabilir- Örnek komut
ls /bin/?,/bin/[dosyasını gösterir
- Örnek komut
/bin/[ve/bin/testaynı ikili dosyayı işaret ediyor olabilir- Örnekte iki yol, aynı inode’a ve boyuta sahip dosyalar olarak görünür
- Ancak her sistemde hard link olmaları gerekmez
test, kabukta ifadeleri değerlendiren bir programdır- Dize karşılaştırma
- Sayı karşılaştırma
- Dosya koşullarını kontrol etme
- Değerlendirme sonucu doğruysa çıkış kodu olarak 0, yanlışsa 1 döndürür
[ neden komut gibi davranır?
test a = bbir koşul ifadesi gibi görünmekte zorlanır; ancak aynı mantık[ a = b ]şeklinde yazıldığında daha tanıdık bir biçim alır[ayrı bir söz dizimi gibi görünse de aslında bir komut çağrısıdırif [ a = b ]; then ... fi,[komutunu çalıştırır ve çıkış kodunu kontrol eder[olarak çağrıldığında program, son argümanın kapatan köşeli parantez]olup olmadığını denetler
ififadesi koşulu doğrudan yorumlamak yerine, verilen komutun çıkış koduna göre dallanırtest a = a; echo $?sonucu0test a = b; echo $?sonucu1[ a = a ]; echo $?sonucu0[ a = b ]; echo $?sonucu1
- Aynı bağlamda
truevefalseda çıkış kodu döndüren yardımcı ikili dosyalar olarak görülebilir
Harici ikili dosya ile kabuk yerleşik komutu arasındaki fark
testve[kabuk betiklerinde sık kullanıldığı için çoğu kabuk bunları yerleşik komut olarak da uygular- Aynı girdi için bile harici ikili dosya ile kabuk yerleşik komutunun çıktısı farklı olabilir
/bin/test a bçıktısıtest: a: unexpected operatortest a bçıktısıdash: 2: test: a: unexpected operator
- Bu tür farklar yalnızca
testve[için değil,echogibi basit görünen komutlarda da ortaya çıkabilir - Yerleşik uygulama kabuktan kabuğa değiştiğinden, betiğin davranışı onu çalıştıran kabuğa göre farklılaşabilir
Bash uzantısı [[nin uyguladığı ayrı kurallar
[[bir Bash uzantısıdır ve[kullanımının yerine geçebilir- En büyük fark,
[[nin her zaman yerleşik bir söz dizimi olmasıdır- Harici ikili dosya olarak çalıştırılabilen
[den farklı olarak,[[içinde Bash ifade içindeki dil kurallarını değiştirebilir
- Harici ikili dosya olarak çalıştırılabilen
- Glob örneğinde
[ve[[birbirinden farklı davranırtouch long-namesonrasında[ long* = long-name ] && echo match,matchyazdırır[komutunun argümanlarına normal kabuk genişletme kuralları uygulanır velong*, dizindekilong-nameolarak genişletilir[[ long* = long-name ]] && echo matchçıktı üretmez[[,long*değerini literal dize olarak ele alıplong-nameile aynen karşılaştırdığı için başarısız olur
- Bash’e özel betiklerde
[[ile düzenli ifade eşleştirme=~gibi özellikler de kullanılabilir
Betiklerde hangisi seçilmeli?
- Taşınabilir kabuk betikleri için
[kullanmak daha doğrudur testde kullanılabilir, ancak yaygın tercih değildir- Betik yalnızca Bash’e özelse
[[yi tutarlı biçimde kullanmak daha iyidir - Kabuğun kendisinde de
!,&&,||gibi ifade operatörleri vardır- Bu operatörler komutların çıkış durumuna göre çalışır
grep ^hello$ ... && grep ^bye$ ..., iki komut da başarılı olursa toplam çıkış kodunun0olmasını sağlar- İlk
grepbaşarısız olursa&&sonrasındaki komut da başarılı olmayacağından toplam çıkış kodu1olur
- Bu nedenle
test/[ifadeleri ile kabuğun mantıksal operatörleri tek bir koşul ifadesi içinde birleştirilebilir- Örnek:
[ a = b ] || grep -q ^hello$ /usr/share/dict/words
- Örnek:
- POSIX,
/bin/[ve/bin/testdosyalarının hard link olmasını şart koşmaz- NetBSD’de hard link idi
- macOS Catalina, aynı ikili dosyanın ayrı kopyalarını sağlar
- Debian testing, birbirinden farklı ikili dosyalar sağlar
- POSIX belirtimi, iki dosyanın link olmasını şart koşmaz
1 yorum
Hacker News görüşleri
Orijinal yazının yazarı benim. Paylaştığınız için teşekkürler; ön sayfaya kadar çıkmasına sevindim. Başlıkta muhtemelen (2020) yer almalı ve "test" gerçek bir komutu ifade ettiği için büyük harfle yazılmaması daha doğru olur.
2021'de yazdığım ilgili bir yazı daha var; onda bash'in
[[operatörüne kadar değiniyorum, bu bağlamda ilginç gelebilir: https://jmmv.dev/2021/08/useless-use-of-gnu.html[[teknik olarak yerleşik bir komut değil, özünde bir sözdizimi öğesine daha yakın. Muhtemelen içeride neredeyse erişilemeyen bir yerleşik komut kullanıyordur, ama ilginç olan şu ki]]de anlamlı bir konuma gelemeyen ayrılmış bir sözcük olmasına rağmen yine de ayrılmış sözcüktür.Bash dışı bazı kabuklarda belirli türde fonksiyon bildirmek için
functionanahtar sözcüğü gerekir.makeiçindeki$(shell), çok sayıda hedef derlenirken ölçülebilir bir performans farkı yaratabilir. Yine de hiçbir iş yapmıyorsa zarardır; bu yüzden genelde yeniden üretimi tetiklemek istiyorsanızincludekullanmak daha doğrudur. GNU'nun POSIX'i görmezden gelmesi tamamen makul, çünkü POSIX çoğu gerçek problemi çözmekte pek faydalı değil.olmadan aramak da faydalı, az önce yazdığınız komuta sonradan seçenek ekleyebilmek de gerçekten çok pratik. Bunu desteklemeyen komutları görünce hep sinir oluyorum.Betiklerde POSIX
sh'ye uymak genelde mantıklı. En azından Bash'e özgü sözdizimi kullandığınızın farkında olmalısınız--ignore-case,set -o pipefailgibi GNU genişletmelerini kullanmanın betiğin taşınabilirliğini azalttığından şikâyet ediyor. Bu kendi başına doğru.Ama Linux kullanıcılarının neden taşınabilirliği bu kadar önemsemesi gerektiğini açıklamıyor. OpenBSD ve FreeBSD hayatta ve iyi durumda ama kullanıcı sayıları o kadar az ki özellikle endişe edilmesi gereken bir kitle gibi görünmüyor. Hakkaniyet adına böyle işletim sistemlerini de düşünmek gerekir denebilir, ama bu çizgi nerede biter?
vxWorksgibi obscure şeyleri de mi hesaba katmalıyız? BusyBox ve Alpine tarafı daha ilginç, ama değişiklikler o kadar büyük ki zaten neredeyse her zaman ayrı bir portlama gerekiyor. GNU dışı ekosistemleri önemsemek için başka ikna edici bir neden var mı?if [ a = b ] || grep -q ^hello$ /usr/share/dict/words; then echo "test failed and grep succeeded"; figibi bir şey, sıradan günlük kabuk kullanımı değil mi?make $(shell …) expansionolarak gösteriliyor, ama aslındamake $(shell ...) expansionolması gerekiyordu.Metnin gövdesinde doğru yazıldığı gibi bu bir üç nokta değil, tek bir ellipsis de değil; dolayısıyla
mldrde doğru değil. Muhtemelen birbiriyle ilgisiz iki ayrı hata aynı anda etkili olmuşSon noktayı bir adım daha ileri götürürsek
ifbloğunun kendisini de kaldırabilirsiniz.if [ a = b ]; then echo "Oops!"; else echo "Expected; phew!"; fi,[ a = b ] && echo "Oops!" || echo "Expected; phew!"hâline gelir.Bunu ne kadar sık yapmak gerekir bilmiyorum ama
[ "$debug" ] && echo "what's going on" >&2gibi standart hataya koşullu debug çıktısı vermek için bazen faydalı oluyor.ifbloğunun sıradan bir komutu sınaması sayesindeif grep -q 'debug' /var/log/nginx/access.log; then echo "Debug request found!"; figibi şeyler de mümkün. Henüz incelemediğim konu, bunu[ $(expr 1 + 1) -eq 2 ] && [ $(expr 2 + 2) -eq 3 ]şeklinde mi yazmak gerektiği, yoksatestkomutunun yerleşik mantıksal VE'si olan[ $(expr 1 + 1) -eq 2 -a $(expr 2 + 2) -eq 4 ]biçiminin mi tercih edilmesi gerektiği. Performans sorun değilse iki yaklaşım da benzer nedenlerle makul görünüyor[ a = b ] && echo "Oops!" || echo "Expected; phew!"ifadesini genel bir kural gibi benimsememek gerekir. Bash bu satırı muhtemelen([ a = b ] && echo "Oops!") || echo "Expected; phew!"şeklinde yorumlar.Bu yüzden
&&sonrasındaki komut dizisi başarısız olursa||sonrasındaki kod her durumda çalışır. Örneğin>/dev/full echo "strings match"yazma hatasıyla başarısız olursa, dizgeler eşit olduğu hâlde"strings don't match"yazdırılır. Bu,ifbloğunun anlamıyla aynı değildirset -eayarını kullanıyorsanız,if [ a = b ]; then echo "Oops!"; fibeklendiği gibi çalışır, ama[ a = b ] && echo "Oops!"ifadesiaifadesib'ye eşit olmadığında hatayla sonlanır-a,-oikili temel ifadeleri ile(,)operatörleri kullanımdan kaldırılmak üzere işaretlenmiştir. Ayrıntılar için https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t... içindeki "Application Usage" bölümüne bakabilirsiniz-akullansanız da iki test ve&&kullansanız da, bash'in aritmetik değerlendirmesini kullanabiliyorsanızexprçalıştırmak için dışarı çıkmanıza gerek yok:[ $((1+1)) -eq 2 ]Birkaç yıldır
[kullanmayı bıraktım.test, bunun sözdizimi değil de diğerleri gibi bir komut olduğunu daha net vurguluyor. Ayrıcaman bashiçinde arama yapmaktansaman testçok daha rahatman testdeğil,man [kılavuz sayfası da var.Bash'te hızlı bir kopya kâğıdı olarak
help testmevcut.[komutu çok eskidir; 1979'daki Version 7 Unix'te zaten bulunuyorduKatılıyorum.
[ve bash’e özgü[[, gerçekte neler olduğuna dair çok fazla kafa karışıklığı yaratıyor; bu yüzden emin olmak zorduYine de
[[için yerleşik komut olmasının garanti edilmesi, kabuk betiği performansının anlamlı olduğu dönemlerde açıkça bir amaca hizmet ediyordu; hem de o kadar eski bir konu değiltestkullanacağım. Sık sık bash betiği yazmadığım içinififadelerinde, özellikle de boşluk kurallarında, hep takılıyorum. Sebebini görünce çok bariz geldi vetestkullanınca bunun sadece argüman geçirmekten ibaret olduğu daha net görünüyor[vetestile ilgili en büyük tuzak, tek argümanlı davranış. Örneğin bir değişkenin boş olmadığını kontrol etmek için[ -n $FOO ]yazabilirsinizAma
FOOayarlanmamışsa boş dizeye değil, hiçbir şeye genişler; yani[ -n ]ile aynı hale gelir. POSIX,[komutunun tek argümanlı biçiminde, o argümanın — burada"-n"— boş olmaması halinde başarılı olmasını şart koşar. Bu yüzden$FOOboş değilmiş gibi yanlış sonuç verir. Değişkenleri mutlaka tırnak içine almalısınıztestyerleşik komutunun tanımında değil, doğrudan kabuğun kendisindeSöz konusu davranış mantıklı. Çünkü
[ "$FOO" ], içeriği ne olursa olsun, hatta"-n"olsa bile, her zaman boş olup olmadığını sınayan bir biçim$FOOiçinde boşluk varsa birden çok argümana genişler. Kısacası her zaman değişkenleri tırnak içine alın[ x"$FOO" != x"" ][ -n "${FOO?}" ]kullanırdım; böylece$FOOnull ya da tanımsızsa betik hemen dururchubot,
test/[/[[ile ilgili daha incelikli noktaları kurcalayan ilginç bir belge yazmış. O blogdaki diğer yazılar da kabuğun tuhaf yanlarını epey ilginç biçimde açıklıyor¹ https://www.oilshell.org/blog/2017/08/31.html
² https://www.oilshell.org/blog/2016/11/18.html
[’ın bir program olduğunu hiç bilmiyordum ve son argümanın kapanış köşeli parantezi olup olmadığını kontrol etmesi biraz komikYine de bu, köşeli parantezlerin iki yanında neden boşluk gerektiğini açıklıyor
[[bash’e özgü. Yalnızca bash kullanacağınızı biliyorsanız kullanın. Ayrıntıları yazı iyi ele alıyorhttps://zsh.sourceforge.io/Doc/Release/Conditional-Expressio...
[[kullanabilirsinizzsh ve ksh’de de var; hatta 1988’de ya da daha önce ksh ile başladığından neredeyse eminim
testve[POSIX’te tanımlıdır ve genelde gerçek ikililer olarak bulunur. Ama kabuk yerleşik komutları bunları gölgeleyebilirBuna karşılık
[[, POSIX’te tanımlı değildir ve genelde yalnızca bir kabuk yerleşiği olarak bulunurSadece en küçük ortak kabuğu hedeflemek tamamen antik bir kaygı gibi geliyor
Son
ififadesinin neden kafa karıştırıcı olduğunu pek anlayamadım. Kabuk betiğini ilk öğrenirken çoğu kişinin[’ı sadece başka bir program değil, bash betik dilinin bir parçası sandığını kastediyorsa şimdi anlıyorum. Yoksa neden şaşırtıcı olduğunu açıklaması iyi olurdu[’ın bir ikili olduğunu bilmeseniz bile neden kafa karıştırıcı olduğunu anlamıyorum. Gayet sıradan bir bash gibi görünüyorKabuk konusunda güçlü tercihleri olan biriyim ve bu tercihler dünyanın geri kalanıyla pek uyuşmuyor
[asla kullanılmamalı, sadecetestkullanılmalı diye düşünüyorum.[onun mekanizmasını dil sözdiziminin bir parçasıymış gibi düşündürüyor, ama gerçekte sadece bir başka "program". Buradaki "program"a yerleşik komutlar ve fonksiyonlar da dahil.if/||/&&çıkış durumuna bakar; programlar ise genişletmeden sonra yalnızca dize olan sihirli$?değişkenine bakmak dışında başka şeylerin çıkış durumunu göremez.casedizelere bakar, ama çıkış durumuna göre çalışmaz vecase ... esacişleyişinin bir parçası olarak çıkış durumu da ayarlamaz. Çıkış durumunu ayarlayan şey "program"dır. Ayrıca[/testyalnızca dosya sistemi yapısını değerlendirmek için kullanılmalı; örneğintest -f /dev/nullgibi. Dize değerlendirmelerinde isecasekullanılmalı diye düşünüyorum. Doğal olarak çoğu betiği görünce içim kaşınıyor, benim yazdıklarımı da başkaları garip buluyorKabuk kullanırken
iften sonra programı ayrı satıra koyup sonrathene geçmeyi tercih ediyorum. Çünkü bu,ifin "thenden önceki son komutun çıkış durumuna baktığını" vurguluyor. Meselaif; ls /tmp/goober; test -d /tmp/goober; echo "I'll always execute the then clause because echo will always return a 0 return code"; then ... figibi bir yapıdaifsonuçta sadeceechonun çıkış durumuna baktığı içinthenbölümü her zaman çalışırtestiçeren betiklerim aynı fikirde olduğumun kanıtı. Yalnız bir yol... Bunun suçlusu olarak Google kabuk stil kılavuzunu görüyorumHem
shhembashüzerinde çalışması gereken betikler yazarkentesttercih etme alışkanlığı edindim, ama köşeli parantez karakteri[’ı komutmuş gibi ele almaktan daha anlamlı geldiği için bunu sürdürdüm.]’nin ayrı bir ikili değil de[’ın argümanı olması da tuhaf. Teknik nedenini anlıyorum ama hack gibi hissettiriyortestkullanan onlarca kişi var gibi görünüyor. Onlarca kişi!Bunu, yalnızca regex eşleştirmesi yapmak istediğinizde
[[kullanmak şeklinde öğrenmiştim. Örneğin:if [[ "${foo}" =~ ^bar$ ]]; then echo Yes; fiOnun dışında sadece
"test"ya da"["kullanıyorum. 85.000 satırdır bash yazıyorum. Bash’in harika olduğunu söylemiyorum ama hâlâ birçok işte ihtiyacımı karşılıyorexpr, temel düzenli ifadeleri eşleştirebilir ve yakalama gruplarını da döndürebilir1: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e...
[ "$foo" = bar ] && echo Yesolması yeterli.Alt dize eşleştirmesi için çoğu durumda
[ve*glob kullanmak yeterlidir. Mesela[ "$bar" = extra* ] && echo '$bar began with extra'gibi. Bash'in regex lehçesi ilkel olduğu için özellikle kullanmaya pek değmez. Karmaşık işler içingrep,awk,perlgibi başka araçları kullanmak daha doğru. Daha yüksek yeniden kullanılabilirlik, modülerlik ve yerleşik tipler gerektiren karmaşık işlerin hepsini bash ile yapmaya inat edersen, getirisi hızla azalır.