Bash'te test ile [ ] aynı yerleşik komuttur; [ yazımında tek fark, son argümanın ] olma zorunluluğudur. [[ ]] ise bir komut değil, Bash'in ayrılmış kelimesidir: içindeki değişkenler kelime bölmeye ve dosya adı genişletmesine uğramaz, == sağ tarafı bir desen olarak eşleştirilir, =~ ile düzenli ifade kullanılabilir ve &&, ||, <, > operatörleri kaçış gerekmeden yazılır. Kısacası POSIX sh uyumluluğu gerekiyorsa [ ], yalnızca Bash'te çalışacak betiklerde [[ ]] daha güvenli seçimdir.
Bu yazıda iki yazımın neden farklı davrandığını, kılavuzun 6.4 bölümündeki koşul ifadelerini (metin, sayı ve dosya testleri), test komutunun argüman sayısına göre karar verme kurallarını ve bu kuralların yol açtığı tipik hataları göreceksiniz. Koşulları if içinde nasıl kullanacağınızı Bash if elif else yazısında, düzenli ifade eşleştirmesini ise Bash regex ve BASH_REMATCH yazısında bulabilirsiniz.
test ve [ aynı komuttur

Kılavuz test ve [ komutlarını tek bir başlık altında anlatır: ikisi de bir koşul ifadesini değerlendirir ve doğruysa 0, yanlışsa 1 döndürür. Her operatör ve her işlenen ayrı bir argüman olmak zorundadır. [ biçiminde son argüman ] olmalıdır; bu, kapanan bir sözdizimi işareti değil, komutun beklediği bir argümandır. type yerleşik komutu ile farkı kendiniz de görebilirsiniz:
#!/usr/bin/env bash
type -t test
type -t [
type -t [[
# çıktı:
# builtin
# builtin
# keyword
[ 1 = 1
# çıktı: ornek.sh: line 10: [: missing `]'
# $? = 2Son satırdaki hata, ] argümanı unutulduğunda [ komutunun 2 döndürdüğünü gösteriyor. Kılavuz, yerleşik komutların hatalı kullanımda 2 döndürdüğünü belirtir. Koşul sonucu 1 (yanlış) ile kullanım hatası 2 arasındaki bu fark, if içinde ikisi de "başarısız" sayıldığı için gözden kaçabilir.
[[ ise bir ayrılmış kelime olduğundan Bash onu komut satırını ayrıştırırken tanır. Bu sayede içindeki kelimelere farklı kurallar uygulayabilir: kılavuza göre [[ ile ]] arasındaki kelimeler tilde, parametre, aritmetik, komut ve süreç ikamesinden ve tırnak kaldırmadan geçer, ama kelime bölme ve dosya adı genişletmesi yapılmaz. Yazının geri kalanındaki farkların neredeyse tamamı bu tek cümleden doğar.
Kelime bölmenin etkisi: en büyük fark
Tek köşeli parantezde tırnaksız bir değişken genişledikten sonra boşluklarından bölünür ve içindeki *, ? gibi karakterler dosya adı deseni olarak yorumlanabilir. Boş bir değişken ise tamamen kaybolur. Sonuç olarak test komutuna giden argüman sayısı değişir ve komut sizin sorduğunuzdan farklı bir soruyu yanıtlar:
#!/usr/bin/env bash
dosya="yıllık rapor.txt"
touch "./$dosya"
[ -f ./$dosya ]
# çıktı: ornek.sh: line 5: [: ./yıllık: binary operator expected
[ -f "./$dosya" ]
# $? = 0
[[ -f ./$dosya ]]
# $? = 0İlk satırda [ komutu -f, ./yıllık, rapor.txt ve ] argümanlarını alır; üç işlenenli bir ifadede ortadaki kelimenin ikili operatör olmasını beklediği için hata verir. Çift tırnak sorunu çözer. [[ ]] içinde ise tırnak gerekmez, çünkü orada kelime bölme hiç yapılmaz. Yine de alışkanlık olarak değişkenleri çift tırnağa almak zarar vermez; tek istisna, birazdan göreceğiniz desen eşleştirmesinde sağ taraftır.
Argüman sayısına göre karar: test komutunun kuralları
test komutunun davranışını anlamanın anahtarı, kılavuzdaki argüman sayısı kurallarıdır. Sıfır argümanda ifade yanlıştır. Tek argümanda ifade, yalnızca argüman boş değilse doğrudur. İki argümanda ilk argüman ! ise ikincinin boş olması sorulur; ilk argüman -f, -z gibi tekli bir operatörse o test uygulanır. Üç argümanda ortadaki kelime ikili bir operatörse karşılaştırma yapılır. Dört ve daha fazla argümanda operatör önceliğine göre ayrıştırma devreye girer.
Bu kuralların pratikteki en şaşırtıcı sonucu şudur: [ -n $bos ] ifadesi, değişken boşken doğru döner. Değişken kaybolduğu için komut yalnızca -n argümanını görür ve tek argüman kuralına göre "-n boş olmayan bir metin mi?" diye sorar. Cevap evettir.
#!/usr/bin/env bash
bos=""
[ -n $bos ]
# $? = 0
[ -n "$bos" ]
# $? = 1
[[ -n $bos ]]
# $? = 1
[ ]
# $? = 1
[ "" ]
# $? = 1
[ ! "" ]
# $? = 0Tırnaklı biçim ve [[ ]] doğru sonucu verir. Bu örnek, "tek köşeli parantezde her değişkeni tırnağa alın" kuralının neden bir stil tercihi değil zorunluluk olduğunu gösteriyor.
Metin testleri
Metinler için kullanılan temel ifadeler şunlardır: -z metin uzunluk sıfırsa, -n metin ya da yalnızca metin uzunluk sıfır değilse doğrudur. = ve == eşitliği, != eşitsizliği sınar. Kılavuz POSIX uyumu için test ile = kullanılmasını önerir; Bash [ ] içinde == da kabul eder ama bu, dash gibi başka kabuklarda çalışmayabilir.
< ve > sözlük sırasına göre karşılaştırma yapar ve iki yazım arasında iki önemli fark vardır. Birincisi, [ ] sıradan bir komut olduğundan < ve > kabuk tarafından yönlendirme olarak algılanır; ters bölü ile kaçırmanız gerekir: [ a \< b ]. İkincisi, kılavuza göre [[ ]] sıralamayı geçerli yerel ayara (locale) göre yaparken POSIX modunda olmayan test ASCII sırasını kullanır. Türkçe karakter içeren metinlerde iki yazımın farklı sonuç vermesi bu yüzden mümkündür.
#!/usr/bin/env bash
[[ elma < armut ]]
# $? = 1
[ elma \< armut ]
# $? = 1
sayi=10
[[ $sayi > 9 ]]
# $? = 1
[[ $sayi -gt 9 ]]
# $? = 0Son iki satır klasik bir tuzaktır: > metin karşılaştırmasıdır ve "10" sözlük sırasında "9"dan önce gelir, çünkü ilk karakterler olan "1" ile "9" karşılaştırılır. Sayıları karşılaştırırken mutlaka -gt gibi sayısal operatörleri ya da (( sayi > 9 )) yazımını kullanın.
Sayı karşılaştırmaları ve [[ ]] içinde aritmetik
Sayısal ikili operatörler -eq (eşit), -ne (eşit değil), -lt (küçük), -le (küçük veya eşit), -gt (büyük) ve -ge (büyük veya eşit) operatörleridir. İşlenenler pozitif ya da negatif tamsayı olabilir. Kılavuzdaki önemli ayrıntı şudur: [[ ]] ile kullanıldıklarında işlenenler aritmetik ifade olarak değerlendirilir. Bu, [ ] ile [[ ]] arasında beklenmedik farklar doğurur:
#!/usr/bin/env bash
[ 3+4 -eq 7 ]
# çıktı: ornek.sh: line 2: [: 3+4: integer expression expected
[[ 3+4 -eq 7 ]]
# $? = 0
[ 08 -eq 8 ]
# $? = 0
[[ 08 -eq 8 ]]
# çıktı: ornek.sh: line 9: [[: 08: value too great for base (error token is "08")
[[ abc -eq 0 ]]
# $? = 0[ ] yalnızca düz tamsayı kabul eder ve 3+4 için hata verir; [[ ]] ise bunu hesaplar. Başında sıfır olan değerler ters yönde sorun çıkarır: aritmetik bağlamda baştaki 0 sekizlik taban demektir ve 8 bu tabanda geçersiz bir rakamdır. Tarih ve saat parçaları ("08", "09") ile çalışırken bu hatayla sık karşılaşılır; çözümü Bash aritmetik işlemler yazısında, 10# öneki ile anlatılıyor. Son örnekte abc bir değişken adı olarak yorumlanır; tanımsız değişken aritmetikte 0 sayıldığından ifade doğru çıkar. Dışarıdan gelen değerleri [[ ]] içinde sayısal karşılaştırmaya sokmadan önce gerçekten sayı olduklarını kontrol etmeniz bu yüzden önemlidir.
Desen eşleştirme: == sağ tarafı bir desendir
[[ ]] içinde == ve != kullanıldığında sağdaki metin, dosya adı genişletmesindeki kurallarla aynı şekilde bir desen olarak eşleştirilir; kılavuz bu eşleşmenin extglob açıkmış gibi yapıldığını da belirtir. = burada == ile aynıdır. Desenin herhangi bir kısmını tırnağa alırsanız o kısım harfi harfine eşleşir. Desen bir değişkende duruyorsa, değişkeni tırnaksız yazmak onu desen olarak, tırnaklı yazmak düz metin olarak kullanır:
#!/usr/bin/env bash
ad="rapor_2026.csv"
[[ $ad == *.csv ]]
# $? = 0
[[ $ad == "*.csv" ]]
# $? = 1
desen="rapor_*"
[[ $ad == $desen ]]
# $? = 0
[[ $ad == "$desen" ]]
# $? = 1
[[ x == @(x|y) ]]
# $? = 0Son satır, shopt -s extglob yazmadan da @(x|y) gibi genişletilmiş desenlerin [[ ]] içinde çalıştığını gösteriyor. Desen dili için joker karakterler ve extglob yazılarına bakabilirsiniz. Desen eşleşmesi tüm metni kapsamalıdır; metnin herhangi bir yerinde eşleşme arıyorsanız deseni *parça* biçiminde yazın ya da =~ ile düzenli ifade kullanın. nocasematch kabuk seçeneği açıksa eşleşme büyük ve küçük harf ayrımı yapmadan yapılır.
Dosya testleri

Kılavuzun 6.4 bölümündeki tekli operatörlerin çoğu dosyalarla ilgilidir. Aksi belirtilmedikçe sembolik bağlantıları izler ve bağlantının hedefini test eder; bağlantının kendisini sınayan -h ve -L bunun istisnasıdır. En sık kullanılanlar şunlardır:
| İfade | Ne zaman doğru |
|---|---|
| -e dosya | Dosya var (türü fark etmez) |
| -f dosya | Var ve normal bir dosya |
| -d dosya | Var ve bir dizin |
| -s dosya | Var ve boyutu sıfırdan büyük |
| -r / -w / -x dosya | Var ve okunabilir / yazılabilir / çalıştırılabilir |
| -h dosya veya -L dosya | Var ve sembolik bağlantı |
| a -nt b | a, b'den daha yeni ya da a var, b yok |
| a -ot b | a, b'den daha eski ya da b var, a yok |
| a -ef b | İkisi aynı aygıt ve inode numarasını gösteriyor |
#!/usr/bin/env bash
: > ./bos.log
echo "kayıt" > ./dolu.log
for f in ./bos.log ./dolu.log ./yok.log; do
if [[ ! -e $f ]]; then
echo "$f yok"
elif [[ -s $f ]]; then
echo "$f dolu"
else
echo "$f boş"
fi
done
# çıktı:
# ./bos.log boş
# ./dolu.log dolu
# ./yok.log yok
[[ ./dolu.log -nt ./yok.log ]]
# $? = 0Son satır -nt tanımının ikinci yarısını gösteriyor: karşılaştırılan dosya yoksa var olan dosya "daha yeni" sayılır. Bu, "çıktı dosyası yoksa ya da kaynaktan eskiyse yeniden üret" türündeki kontrolleri tek ifadeyle yazmanızı sağlar: [[ kaynak -nt hedef ]]. Okuma, yazma ve çalıştırma izinlerinin nasıl belirlendiği bu yazının konusu değil; izin sistemi için chmod ve dosya izinleri rehberine bakın. Windows üzerindeki Git Bash gibi ortamlarda izin bitleri Linux'takiyle aynı şekilde çalışmadığı için -x ve -w sonuçları farklı olabilir; bu testleri gerçek bir Linux sisteminde denemeniz daha doğru olur.
Değişken ve seçenek testleri
Dosyalar dışında iki kullanışlı tekli operatör daha vardır. -v ad, ad isimli kabuk değişkenine bir değer atanmışsa doğrudur; boş değer atanmış olması da "atanmış" sayılır. Bu, "değişken hiç tanımlanmamış" ile "boş" arasındaki farkı ayırt etmenin temiz bir yoludur. -o seçenek ise set -o listesindeki bir kabuk seçeneğinin açık olup olmadığını sınar. Değişkenin adını yazdığınıza, $ ile değerini yazmadığınıza dikkat edin.
#!/usr/bin/env bash
bos=""
[[ -v bos ]]
# $? = 0
[[ -v tanimsiz ]]
# $? = 1
[[ -z $tanimsiz ]]
# $? = 0
set -o noclobber
[[ -o noclobber ]]
# $? = 0-z tanımsız değişken için de doğru döner, bu yüzden iki durumu ayırt edemez. set -u kullanan betiklerde tanımsız bir değişkeni genişletmek hata olduğundan, varlığı önce -v ile sınamak işe yarar; ayrıntılar set -euo pipefail yazısında. Varsayılan değer atama kalıpları için parametre genişletme ve varsayılan değer yazısına bakabilirsiniz.
Koşulları birleştirmek
[[ ]] içinde ifadeler azalan öncelik sırasıyla ( ), !, && ve || ile birleştirilir. && ve || kısa devre yapar: sol taraf sonucu belirlemeye yetiyorsa sağ taraf hiç değerlendirilmez. test ve [ için aynı iş eskiden -a ve -o ile yapılırdı. Kılavuz, POSIX standardının bu operatörleri ve parantezle gruplamayı kullanımdan kaldırdığını, dört ve daha fazla argümanda belirsizlik yaratabildiklerini ve yerlerine her testi ayrı yazıp kabuğun && ve || liste operatörleriyle bağlamanın daha güvenilir olduğunu söyler.
#!/usr/bin/env bash
touch ./veri.txt
[[ -f ./veri.txt && ! -d ./veri.txt ]]
# $? = 0
[ -f ./veri.txt ] && [ ! -d ./veri.txt ]
# $? = 0-a operatörünü [[ ]] içinde ikili "ve" anlamında kullanamazsınız; [[ 1 -eq 1 -a 2 -eq 2 ]] yazmak "syntax error in conditional expression" hatası verir. [[ ]] içinde yalnızca && ve || geçerlidir.
[ ] ile [[ ]] karşılaştırma tablosu
![[ ] ile [[ ]] karşılaştırma tablosu: Türü, POSIX sh uyumu, Tırnaksız değişkende kelime bölme, Dosya adı genişletmesi](/assets/blog/bash-test-ve-cift-koseli-parantez-farki-b10.webp)
| Özellik | [ ] ve test | [[ ]] |
|---|---|---|
| Türü | Yerleşik komut | Ayrılmış kelime |
| POSIX sh uyumu | Var | Yok, Bash ve benzeri kabuklara özgü |
| Tırnaksız değişkende kelime bölme | Yapılır | Yapılmaz |
| Dosya adı genişletmesi | Yapılır | Yapılmaz |
| == sağ tarafı | Düz metin | Desen (extglob dahil) |
| =~ düzenli ifade | Yok | Var |
| < ve > sıralama | Kaçırılmalı, ASCII sırası | Kaçışsız, yerel ayar sırası |
| Mantıksal birleştirme | -a -o (eskimiş) veya ayrı komutlar | && || ! ( ) |
| -eq işlenenleri | Düz tamsayı | Aritmetik ifade |
Sık yapılan hatalar
[ ] içinde büyüktür işaretiyle dosya oluşturmak
Tek köşeli parantez içinde kaçırılmamış > bir yönlendirmedir. [ 10 > 9 ] komutu "10" ile "9"u karşılaştırmaz; [ 10 ] testini çalıştırır, çıktısını da 9 adlı bir dosyaya yönlendirir. Test tek argümanla çalıştığı için her zaman doğru döner ve çalışma dizininizde boş bir dosya belirir. < ise girdi yönlendirmesidir ve dosya yoksa hata verir:
#!/usr/bin/env bash
[ 10 > 9 ]
# $? = 0
[[ -f ./9 ]] && echo "9 adlı dosya oluştu"
# çıktı: 9 adlı dosya oluştu
[ 10 < 8 ]
# çıktı: ornek.sh: line 6: 8: No such file or directorySağ tarafı tırnaklamayı unutmak
İki değişkenin birebir eşit olup olmadığını [[ $a == $b ]] ile sorarsanız, $b içinde * ya da ? gibi bir karakter varsa desen olarak yorumlanır ve yanlış pozitif alırsınız. Eşitlik kontrolünde sağ tarafı her zaman tırnaklayın: [[ $a == "$b" ]].
Operatörlerin çevresinde boşluk bırakmamak
[[ $a==$b ]] yazdığınızda Bash tek bir kelime görür ve bunu "boş olmayan metin mi?" testi olarak yorumlar; sonuç neredeyse her zaman doğrudur. Her operatörün iki yanında boşluk olmalıdır. Aynı kural [ ile ilk argüman ve son argüman ile ] arasındaki boşluk için de geçerlidir.
Küçük bir uygulama: girdi doğrulama
Aşağıdaki fonksiyon, bir yapılandırmadan gelen port değerini doğruluyor. Önce boş olup olmadığına, sonra yalnızca rakamlardan oluşup oluşmadığına (desen ile), en son da aralığa bakıyor. Sayısal karşılaştırmayı rakam kontrolünden sonra yapmak, yukarıda gördüğünüz "abc sıfır sayılır" ve "08 geçersiz taban" sorunlarını önler. +([0-9]) deseni "bir ya da daha fazla rakam" demektir.
#!/usr/bin/env bash
port_dogrula() {
local p=$1
if [[ -z $p ]]; then
echo "boş değer"
elif [[ $p != +([0-9]) ]]; then
echo "$p: sayı değil"
elif [[ $p == 0* ]]; then
echo "$p: başında sıfır olamaz"
elif (( p < 1 || p > 65535 )); then
echo "$p: aralık dışı"
else
echo "$p: geçerli"
fi
}
for deger in "" abc 080 70000 8080; do
port_dogrula "$deger"
done
# çıktı:
# boş değer
# abc: sayı değil
# 080: başında sıfır olamaz
# 70000: aralık dışı
# 8080: geçerliSık Sorulan Sorular
Hangisini kullanmalıyım: [ ] mi [[ ]] mi?
Betiğiniz #!/usr/bin/env bash ile başlıyor ve yalnızca Bash'te çalışacaksa [[ ]] daha az hataya açıktır. Betiğin /bin/sh ile, yani POSIX kabuklarında da çalışması gerekiyorsa [ ] kullanın ve her değişkeni çift tırnağa alın. İki kabuk arasındaki farklar için Bash ve sh farkı yazısına bakabilirsiniz.
unary operator expected hatası ne demek?
Tek köşeli parantezde boş bir değişken tırnaksız kullanıldığında argüman eksilir ve [ komutu ikili operatörü tekli operatör yerinde görür. Değişkeni tırnağa alın ya da [[ ]] kullanın. Diğer yaygın mesajlar için Bash sık hata mesajları yazısına bakın.
Bir değişkenin boş mu tanımsız mı olduğunu nasıl ayırırım?
[[ -v ad ]] değişkene değer atanmışsa (boş değer dahil) doğrudur. [[ -z $ad ]] ise hem boş hem tanımsız durumda doğru döner. İkisini birlikte kullanarak üç durumu da ayırt edebilirsiniz.
test komutu -- argümanını seçeneklerin sonu olarak tanır mı?
Hayır. Kılavuz, test komutunun hiçbir seçenek kabul etmediğini ve -- argümanını da seçeneklerin sonu olarak yorumlamadığını açıkça belirtir. -- yazarsanız sıradan bir metin argümanı olarak değerlendirilir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Bash öğrenme rehberi · if elif else · regex ve BASH_REMATCH · joker karakterler · aritmetik işlemler · Bash ve sh farkı