Bash hata mesajları neredeyse her zaman aynı kalıbı izler: önce betiğin adı, sonra line N ile satır numarası, ardından hatayı veren komut ve kısa bir açıklama gelir; örneğin betik.sh: line 3: ehco: command not found. Bu yazıda en sık karşılaşılan mesajları (command not found, bad substitution, unary operator expected, too many arguments, syntax error near unexpected token, unexpected end of file, unbound variable, ambiguous redirect, Windows satır sonları ve Permission denied) gerçek çıktılarıyla, nedenleriyle ve çözümleriyle tek tek ele alıyoruz.

Örneklerin hepsi ornek.sh adlı bir dosyaya yazılıp GNU Bash 5.2.37 ile çalıştırıldı ve çıktıdaki satır numaraları o dosyanın satırlarıdır (yorum satırları da sayılır). Kendi sisteminizde mesajın başında betik yolunun tamamı veya bash kelimesi görünebilir; asıl bilgi her zaman son iki nokta üst üsteden sonraki kısımdadır. Çıkış kodlarının anlamı için exit kodları yazısına, genel hata ayıklama için set -x ile hata ayıklama rehberine, serinin tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz.

command not found (çıkış kodu 127)

command not found (çıkış kodu 127)

Kılavuzun komut arama kurallarına göre Bash bir komut adını önce fonksiyonlar, sonra yerleşik komutlar arasında arar; bulamazsa PATH içindeki dizinlerde aynı adlı çalıştırılabilir bir dosya arar. Hiçbirinde yoksa hata mesajı yazar ve 127 ile döner. En sık neden basit bir yazım hatasıdır:

bash
#!/usr/bin/env bash
ehco "merhaba"
echo "\$? = $?"
# çıktı:
#   ornek.sh: line 2: ehco: command not found
#   $? = 127

İkinci yaygın neden, değişken atamasında eşittir işaretinin çevresine boşluk koymaktır. Bash'te atama ad=değer biçiminde, boşluksuz yazılır. Boşluk koyduğunuzda ad kelimesi komut adı, = ve "Ayşe" ise onun argümanları olur:

bash
#!/usr/bin/env bash
ad = "Ayşe"
# çıktı: ornek.sh: line 2: ad: command not found
ad="Ayşe"
echo "$ad"
# çıktı: Ayşe

Üçüncü neden, komutun gerçekten kurulu olmaması veya PATH içinde bulunmamasıdır. Cron gibi ortamlar betiği daha kısa bir PATH ile çalıştırabilir; bu durumda komutun tam yolunu yazmak ya da betiğin başında PATH değişkenini ayarlamak sorunu çözer. Ayrıntı için cron job rehberine bakabilirsiniz. Değişken atamasının kuralları Bash değişkenler yazısında anlatılıyor.

Kılavuz, komut bulunamadığında Bash'in son bir adım daha attığını da belirtir: command_not_found_handle adında bir fonksiyon tanımlıysa onu, ayrı bir çalışma ortamında, bulunamayan komutun adı ve argümanlarıyla çağırır; fonksiyonun çıkış kodu komutun çıkış kodu olur. Bazı dağıtımlar bu kancayı "şu paketi kurun" önerisi göstermek için kullanır. Kendi mesajınızı da yazabilirsiniz:

bash
#!/usr/bin/env bash
command_not_found_handle() {
  echo "'$1' adında bir komut yok (argümanlar: ${*:2})" >&2
  return 127
}
yedekle --tam /veri
echo "\$? = $?"
# çıktı:
#   'yedekle' adında bir komut yok (argümanlar: --tam /veri)
#   $? = 127

No such file or directory, Is a directory ve Permission denied

Komut adında eğik çizgi varsa (./betik.sh, /opt/app/run) Bash PATH araması yapmaz, verilen yolu doğrudan çalıştırmayı dener. Dosya yoksa No such file or directory mesajı ve 127 kodu alırsınız. Dosya var ama çalıştırılamıyorsa kılavuza göre dönüş kodu 126'dır. 126'nın en kolay gözlemlenen örneği bir dizini komut gibi çalıştırmaktır:

bash
#!/usr/bin/env bash
mkdir -p ./klasor
./klasor
echo "\$? = $?"
# çıktı:
#   ornek.sh: line 3: ./klasor: Is a directory
#   $? = 126
./olmayan.sh
echo "\$? = $?"
# çıktı:
#   ornek.sh: line 8: ./olmayan.sh: No such file or directory
#   $? = 127

Permission denied mesajı da 126 grubundandır: dosya vardır ama çalıştırma izni yoktur. İki çözümünüz var: dosyaya chmod +x betik.sh ile çalıştırma izni vermek ya da izne ihtiyaç duymadan bash betik.sh ile çalıştırmak. İkinci yazımda dosyayı işletim sistemi çalıştırmaz; kılavuzun betikler bölümünde anlatıldığı gibi Bash dosyayı açıp komutlarını okur. İzinlerin ayrıntısı chmod rehberinde, betik çalıştırma yolları Bash script yazma yazısında.

bad substitution

Bu mesaj, ${...} içindeki ifadenin Bash'in tanıdığı bir parametre genişletme biçimi olmadığını söyler. En sık nedenler şunlardır: değişken adında izin verilmeyen bir karakter kullanmak (nokta, tire), operatörü yanlış yazmak ya da betiği Bash'e ait genişletmeleri tanımayan bir kabukla çalıştırmak. Aşağıda dosya uzantısını silmek isterken nokta yanlış yere yazılmış:

bash
#!/usr/bin/env bash
dosya="rapor.txt"
echo "${dosya.txt}"
# çıktı: ornek.sh: line 3: ${dosya.txt}: bad substitution
echo "${dosya%.txt}"
# çıktı: rapor

Betiğiniz kendi bilgisayarınızda çalışıp sunucuda bad substitution veriyorsa, büyük olasılıkla betik sh betik.sh ile ya da #!/bin/sh satırıyla, Bash olmayan bir kabukta çalışıyordur; ${v^^} veya ${v:0:3} gibi genişletmeler POSIX'te yoktur. Bir başka olasılık da yeni sürümlere ait bir yapıyı eski bir Bash'te denemektir: kılavuz 5.3'teki ${ komut; } biçimi 5.2.37'de aynı hatayı verdi. Ayrıntılar Bash ve sh farkı ve Bash sürümü yazılarında. Doğru genişletme biçimleri için string işlemleri yazısına bakın.

unary operator expected, binary operator expected, too many arguments

unary operator expected, binary operator expected, too many arguments

Bu üç mesaj da [ (yani test) komutundan gelir ve neredeyse her zaman tırnaklanmamış bir değişkenin sonucudur. Kılavuz, test komutunun ifadeyi argüman sayısına göre değerlendirdiğini anlatır. Boş bir değişkeni tırnaksız yazdığınızda o değişken argüman listesinden tamamen kaybolur ve [ $cevap = evet ] ifadesi [ = evet ] hâline gelir. İki argümanlı bu ifadede ilk argüman tekli (unary) bir operatör olmalıdır; = öyle olmadığı için hata alırsınız:

bash
#!/usr/bin/env bash
cevap=""
if [ $cevap = "evet" ]; then echo "onay"; fi
# çıktı: ornek.sh: line 3: [: =: unary operator expected
if [ "$cevap" = "evet" ]; then echo "onay"; else echo "onay yok"; fi
# çıktı: onay yok

Değişken boşluk içeriyorsa tersi olur: değer birden fazla kelimeye bölünür ve test beklediğinden fazla argüman alır. Argüman sayısına göre mesaj binary operator expected veya too many arguments olabilir:

bash
#!/usr/bin/env bash
dosya="yıllık rapor.txt"
if [ -f $dosya ]; then echo "var"; fi
# çıktı: ornek.sh: line 3: [: yıllık: binary operator expected
ad="Ali Veli"
if [ $ad = "Ali Veli" ]; then echo "eşit"; fi
# çıktı: ornek.sh: line 6: [: too many arguments
if [ "$ad" = "Ali Veli" ]; then echo "eşit"; fi
# çıktı: eşit

Çözüm tektir: [ ] içindeki her değişkeni çift tırnak içine alın. Alternatif olarak Bash'e özgü [[ ]] yapısını kullanabilirsiniz; kılavuza göre [[ ]] içinde kelime bölmesi ve dosya adı genişletmesi yapılmaz, bu yüzden boş veya boşluklu değişkenler bu hatalara yol açmaz. İki yapının tüm farkları test ve [[ ]] farkı yazısında anlatılıyor.

integer expression expected

-eq, -lt, -gt gibi sayısal karşılaştırmalar her iki tarafın da tamsayı olmasını ister. Değerde harf, boşluk veya ondalık nokta varsa [ bu mesajı yazar ve 2 döndürür. Kılavuz, yerleşik komutların hatalı kullanımda 2 döndürdüğünü belirtir; 2 değeri "koşul yanlış" anlamındaki 1'den farklıdır:

bash
#!/usr/bin/env bash
adet="12 adet"
[ "$adet" -gt 10 ] && echo "çok"
# çıktı: ornek.sh: line 3: [: 12 adet: integer expression expected
echo "\$? = $?"
# çıktı: $? = 2

Değeri karşılaştırmadan önce temizleyin (örneğin ${adet%% *} ile ilk kelimeyi alın) ya da bir desenle sayı olup olmadığını kontrol edin.

syntax error near unexpected token ve unexpected end of file

Sözdizimi hataları betiğin yapısında bir sorun olduğunu gösterir: bir anahtar kelime eksiktir, fazladır ya da yanlış yerdedir. unexpected end of file, Bash'in dosyanın sonuna geldiğinde hâlâ açık bir yapı (if için fi, do için done, { için } veya kapanmamış bir tırnak) beklediği anlamına gelir. Mesajdaki satır numarası hatanın başladığı yeri değil, dosyanın bittiği yeri gösterir; bu yüzden açık kalan yapıyı yukarıya doğru aramanız gerekir:

bash
#!/usr/bin/env bash
echo "başladı"
if [ -d ./klasor ]; then
  echo "klasör var"
# fi unutuldu
# çıktı:
#   başladı
#   ornek.sh: line 9: syntax error: unexpected end of file

Çıktının ilk satırına dikkat edin: echo "başladı" çalıştı, hata ondan sonra geldi. Kılavuz, Bash'in bir satırı ve bir bileşik komutu oluşturan tüm satırları çalıştırmadan önce okuduğunu anlatır. Bash betiği baştan sona bir kerede doğrulamaz; komutları sırayla okuyup çalıştırır. Yani bozuk bir betik, hatalı yere kadar olan komutları çalıştırmış olabilir. Bunu önlemek için betiği çalıştırmadan önce bash -n ile kontrol edin. Kılavuza göre -n seçeneği komutları okur ama çalıştırmaz:

bash
#!/usr/bin/env bash
printf 'echo basladi\nif true; then\n  echo eksik\n' > ./bozuk.sh
bash -n ./bozuk.sh
echo "\$? = $?"
# çıktı:
#   ./bozuk.sh: line 4: syntax error: unexpected end of file
#   $? = 2

syntax error near unexpected token ise Bash'in o noktada beklemediği bir kelimeyle karşılaştığını söyler ve hemen altında hatalı satırı tekrar gösterir. Aşağıda for satırından sonra do unutulmuş; Bash echo kelimesini görünce durdu:

bash
#!/usr/bin/env bash
echo "başladı"
for i in 1 2 3
  echo "$i"
done
# çıktı:
#   başladı
#   ornek.sh: line 4: syntax error near unexpected token `echo'
#   ornek.sh: line 4: `  echo "$i"'

Parantezler de sık bir nedendir. Bash'te ( alt kabuk açan bir operatördür; metin olarak yazdırmak istiyorsanız tırnak içine almanız gerekir:

bash
#!/usr/bin/env bash
echo (merhaba)
# çıktı:
#   ornek.sh: line 2: syntax error near unexpected token `merhaba'
#   ornek.sh: line 2: `echo (merhaba)'

echo "(merhaba)" yazımı sorunu çözer. Benzer hatalar fonksiyonu ad() diye parantezle çağırmaktan, then veya do sonrasında ; ya da yeni satır unutmaktan ve [ ile ] çevresinde boşluk bırakmamaktan kaynaklanır.

unbound variable

Bu mesajı yalnızca set -u (nounset) açıkken görürsünüz. Kılavuza göre bu seçenek, @ ve * dışındaki tanımsız parametrelerin genişletilmesini hata sayar; etkileşimli olmayan kabuk mesajı yazdıktan sonra çıkar. Seçeneğin amacı, yazım hatası yapılmış değişken adlarını sessizce boş metin olarak kullanmak yerine hemen yakalamaktır. Değişkenin tanımsız olabileceğini biliyorsanız bir varsayılan değer verin:

bash
#!/usr/bin/env bash
set -u
echo "hedef: ${HEDEF_DIZIN:-/tmp/varsayilan}"
# çıktı: hedef: /tmp/varsayilan
echo "hedef: $HEDEF_DIZIN"
echo "buraya gelinmez"
# çıktı: ornek.sh: line 5: HEDEF_DIZIN: unbound variable

${HEDEF_DIZIN:-/tmp/varsayilan} değişken tanımsız veya boşsa varsayılanı kullanır, ${HEDEF_DIZIN-} ise yalnızca tanımsızlık kontrolü yapıp boş metin döndürür. Bu genişletmelerin hepsi varsayılan değer yazısında, set -u ile birlikte kullanılan diğer güvenlik seçenekleri set -euo pipefail rehberinde anlatılıyor.

ambiguous redirect

Yönlendirmenin hedefi olan kelime genişletildikten sonra tam olarak bir dosya adı vermelidir. Değişken boşsa ya da boşluk içerip birden fazla kelimeye bölünüyorsa Bash hangi dosyaya yazacağını bilemez ve bu mesajı verir. Değişkeni tırnaklamak bölünmeyi önler; ama değişken boşsa bu sefer boş adlı bir dosya açılmaya çalışılır ve farklı bir hata alırsınız:

bash
#!/usr/bin/env bash
log_dosyasi=""
echo "kayıt" > $log_dosyasi
# çıktı: ornek.sh: line 3: $log_dosyasi: ambiguous redirect
echo "kayıt" > "$log_dosyasi"
# çıktı: ornek.sh: line 5: : No such file or directory

Asıl çözüm, değişkenin dolu olduğundan emin olmaktır: : "${log_dosyasi:?log dosyası tanımlı değil}" satırı değişken boşsa anlaşılır bir mesajla durur. Yönlendirmelerin kuralları için yönlendirme yazısına bakın.

value too great for base

Kılavuzun aritmetik kurallarına göre başında 0 olan sayılar sekizlik (octal) tabanda yorumlanır. Sekizlik tabanda 8 ve 9 rakamları olmadığından, ay veya gün gibi sıfırla doldurulmuş 08, 09 değerleri aritmetikte hata verir. Sayının onluk olduğunu 10# önekiyle açıkça belirtin:

bash
#!/usr/bin/env bash
ay="08"
echo $(( ay + 1 ))
# çıktı: ornek.sh: line 3: 08: value too great for base (error token is "08")
echo $(( 10#$ay + 1 ))
# çıktı: 9

Aritmetik genişletmenin diğer kuralları için aritmetik işlemler yazısına bakabilirsiniz.

readonly variable

readonly veya declare -r ile salt okunur yapılmış bir değişkene atama yapmaya çalıştığınızda bu mesajı alırsınız. Kılavuza göre salt okunur değişkenler sonradan atama veya unset ile değiştirilemez. BASHOPTS, SHELLOPTS, BASH_VERSINFO, EUID ve PPID gibi bazı kabuk değişkenleri de salt okunurdur:

bash
#!/usr/bin/env bash
readonly SURUM=1
SURUM=2
echo "\$? = $?, SURUM = $SURUM"
# çıktı:
#   ornek.sh: line 3: SURUM: readonly variable
#   $? = 1, SURUM = 1

Aynı kütüphane dosyasını iki kez source ettiğinizde de bu hatayı görebilirsiniz; ikinci yükleme readonly satırını yeniden çalıştırır. Bunu önlemenin yolu source komutu yazısında anlatılıyor.

Windows satır sonları: ^M ve bad interpreter

Windows'ta düzenlenen bir betik her satırın sonunda görünmez bir satır başı karakteri (\r, ekranda bazen ^M olarak görünür) taşır. Linux'ta Bash bu karakteri komutun bir parçası sayar. Sonuç olarak cd ./klasor satırı aslında ./klasor\r dizinine gitmeye çalışır, ilk satırdaki #!/bin/bash ise /bin/bash\r adlı, var olmayan bir yorumlayıcıyı gösterir. Betiği ./betik.sh ile çalıştırdığınızda bu ikinci durum genellikle bad interpreter içeren bir mesajla kendini belli eder; betiği bash betik.sh ile çalıştırdığınızda ise satırların sonundaki \r yüzünden komut veya dosya bulunamadı hataları görürsünüz.

read ile okunan verilerde \r her sistemde korunur ve karşılaştırmaları bozar. printf '%q' görünmez karakteri görünür kılar, ${degisken%$'\r'} ise sondaki satır başı karakterini siler:

bash
#!/usr/bin/env bash
printf 'web-01\r\n' > ./sunucu.txt
read -r sunucu < ./sunucu.txt
echo "uzunluk: ${#sunucu}"
# çıktı: uzunluk: 7
printf '%q\n' "$sunucu"
# çıktı: $'web-01\r'
[[ $sunucu == "web-01" ]] || echo "eşleşmedi: sonda görünmez \r var"
# çıktı: eşleşmedi: sonda görünmez \r var
sunucu=${sunucu%$'\r'}
[[ $sunucu == "web-01" ]] && echo "temizlendi, artık eşleşiyor"
# çıktı: temizlendi, artık eşleşiyor

Betik dosyasının kendisini düzeltmek için düzenleyicinizde satır sonu biçimini LF olarak ayarlayıp dosyayı yeniden kaydetmek en temiz yoldur. Elinizde yalnızca Bash varsa, aynı genişletmeyle dosyayı satır satır temizleyebilirsiniz:

bash
#!/usr/bin/env bash
printf 'echo "satır bir"\r\necho "satır iki"\r\n' > ./windows.sh
while IFS= read -r satir; do
  printf '%s\n' "${satir%$'\r'}"
done < ./windows.sh > ./temiz.sh
bash ./temiz.sh
# çıktı:
#   satır bir
#   satır iki

Sık yapılan hatalar ve hızlı kontrol listesi

Sık yapılan hatalar ve hızlı kontrol listesi: Hata satırına bakın, Değişkenleri tırnaklayın, Kabuğu kontrol edin, Çıkış koduna bakın
  • Hata satırına bakın: Mesajdaki line N çoğu zaman doğru yeri gösterir; unexpected end of file için yukarı doğru açık kalan yapıyı arayın.
  • Değişkenleri tırnaklayın: unary operator expected, too many arguments ve ambiguous redirect hatalarının çoğu tırnakla çözülür.
  • Kabuğu kontrol edin: Kendi makinenizde çalışan bir betiğin sunucuda bad substitution vermesi, betiğin Bash dışında bir kabukla çalıştığını gösterebilir.
  • Çıkış koduna bakın: 127 komut bulunamadı, 126 çalıştırılamadı, 2 hatalı kullanım veya sözdizimi hatası, 128 üstü sinyal anlamına gelir.
  • Önce bash -n: Sözdizimi hatalarını betik hiçbir komut çalıştırmadan yakalar.
  • İzlemeyi açın: bash -x betik.sh her komutu genişletilmiş hâliyle gösterir; ayrıntı hata ayıklama yazısında.

Sık Sorulan Sorular

Hata mesajındaki satır numarası neden yanlış görünüyor?

Çoğu hata için numara doğrudur ve yorum satırları da sayılır. Dosya sonu hatalarında numara dosyanın bittiği yeri, eval içindeki hatalarda ise değerlendirilen metnin içindeki satırı gösterir.

command not found hatası alıyorum ama komut kurulu, neden?

Komutun bulunduğu dizin betiğin çalıştığı ortamdaki PATH içinde olmayabilir; özellikle cron ve servis yöneticileri farklı bir PATH kullanabilir. Komutun tam yolunu yazın veya PATH değişkenini betikte ayarlayın. Komutun nerede bulunduğunu type -a komut ile görebilirsiniz.

Hata mesajlarını bir dosyada toplayabilir miyim?

Evet. Bash hata mesajlarını standart hata akışına (stderr) yazar; bash betik.sh 2>hatalar.log yazımı bu mesajları dosyaya yönlendirir, normal çıktı ekranda kalır. Ayrıntılar stdout ve stderr yönlendirme yazısında.

Betik hata verince neden hemen durmuyor?

Varsayılan olarak Bash, başarısız bir komuttan sonra bir sonraki komutla devam eder. Hata durumunda durmak için set -e kullanabilir ya da kritik komutların çıkış kodunu || exit 1 ile kendiniz kontrol edebilirsiniz.

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 · exit kodları · set -x ile hata ayıklama · test ve [[ ]] farkı · set -euo pipefail · tırnak farkı