Bash scripting öğrenmeye karar verdiğinde genellikle ilk engel değişkenler ve veri tipleriyle uğraşmak oluyor. Python veya Java gibi dillerden geliyorsan Bash’in bu konudaki yaklaşımı seni biraz şaşırtabilir. Bash, tip güvenliği konusunda oldukça gevşek davranır ve bu hem bir avantaj hem de potansiyel hata kaynağıdır. Bu rehberde değişkenleri, dizileri ve veri tiplerini gerçek dünya senaryolarıyla ele alacağız.
Bash’te Değişken Temelleri
Bash’te değişken tanımlamak son derece basittir. Ancak dikkat etmen gereken birkaç kritik nokta var.
#!/bin/bash
# Doğru kullanım
SUNUCU_ADI="web01.sirket.com"
PORT=8080
AKTIF=true
# YANLIŞ kullanım - eşittir işareti etrafında boşluk OLMAMALI
# SUNUCU_ADI = "web01.sirket.com" # Bu hata verir!
echo "Sunucu: $SUNUCU_ADI"
echo "Port: $PORT"
Değişken isimlerinde büyük harf kullanmak zorunlu değil ama geleneksel olarak ortam değişkenleri büyük harfle, yerel script değişkenleri ise küçük harfle yazılır. Bu kurala uymak kodun okunabilirliğini ciddi ölçüde artırır.
Değişken Alıntılama: Tek Tırnak vs Çift Tırnak
Bu konu çoğu sysadmin’in başlangıçta kafasını karıştırır.
#!/bin/bash
KULLANICI="ahmet"
# Çift tırnak: değişken genişletme yapılır
echo "Merhaba $KULLANICI" # Çıktı: Merhaba ahmet
# Tek tırnak: değişken genişletme yapılmaz, her şey literal
echo 'Merhaba $KULLANICI' # Çıktı: Merhaba $KULLANICI
# Süslü parantez kullanımı - değişken sınırlarını belirtir
DOSYA="yedek"
echo "${DOSYA}_2024.tar.gz" # Çıktı: yedek_2024.tar.gz
echo "$DOSYA_2024.tar.gz" # Çıktı: .tar.gz (DOSYA_2024 boş!)
Süslü parantez kullanımı bir alışkanlık meselesi ama özellikle değişken adının hemen ardından başka karakterler geliyorsa mutlaka süslü parantez kullan.
Değişken Kapsam ve export
Bash’te varsayılan olarak her değişken o scriptin kapsamındadır. Alt süreçlere değişkeni aktarmak için export kullanman gerekir.
#!/bin/bash
YEREL_DEG="sadece bu scriptte"
export GLOBAL_DEG="alt processlere de gider"
# Alt script çağrısı
bash alt_script.sh
# Fonksiyon kapsamı
bir_fonksiyon() {
local fonksiyon_degiskeni="sadece fonksiyon içinde"
echo "Global erişilebilir: $GLOBAL_DEG"
echo "Yerel: $fonksiyon_degiskeni"
}
bir_fonksiyon
echo "Fonksiyon değişkeni burada boş: '$fonksiyon_degiskeni'"
Fonksiyonlarda mutlaka local kullan. Kullanmazsan değişken isim çakışmaları can sıkıcı bug’lara yol açar.
Veri Tipleri: Bash’in Gevşek Yaklaşımı
Bash’te her şey temelde bir string’dir. Ancak declare komutuyla değişkenlere özellik atayabilirsin.
Sayısal İşlemler
#!/bin/bash
# Aritmetik işlemler için (( )) kullan
sayi1=10
sayi2=3
toplam=$((sayi1 + sayi2))
carpim=$((sayi1 * sayi2))
bolum=$((sayi1 / sayi2)) # Tam sayı bölümü: 3
kalan=$((sayi1 % sayi2)) # Mod: 1
echo "Toplam: $toplam"
echo "Çarpım: $carpim"
echo "Bölüm: $bolum"
echo "Kalan: $kalan"
# declare ile integer tanımlama
declare -i disk_esigi=85
disk_esigi="yuzde" # Integer declare edildiği için 0 olur
echo "Eşik: $disk_esigi"
# Ondalıklı sayılar için bc kullan
ortalama=$(echo "scale=2; $sayi1 / $sayi2" | bc)
echo "Ondalıklı bölüm: $ortalama"
Gerçek hayatta disk kullanım scriptleri yazarken ondalıklı hesaplama için bc kullanmak sık karşılaştığım bir durum. scale=2 ile kaç ondalık basamak istediğini belirtiyorsun.
String İşlemleri
String manipülasyonu Bash’in en güçlü yanlarından biri.
#!/bin/bash
DOSYA_YOLU="/var/log/nginx/access.log"
# String uzunluğu
echo "Uzunluk: ${#DOSYA_YOLU}"
# Alt string alma
echo "İlk 8 karakter: ${DOSYA_YOLU:0:8}"
# Prefix silme (en kısa eşleşme)
echo "Son kısım: ${DOSYA_YOLU#/var/}"
# Prefix silme (en uzun eşleşme)
echo "Dosya adı: ${DOSYA_YOLU##*/}" # Çıktı: access.log
# Suffix silme
echo "Uzantısız: ${DOSYA_YOLU%.*}" # Çıktı: /var/log/nginx/access
# String değiştirme
echo "Değiştirilmiş: ${DOSYA_YOLU/nginx/apache}"
# Büyük/küçük harf (Bash 4+)
METIN="Sunucu Durumu"
echo "Büyük: ${METIN^^}"
echo "Küçük: ${METIN,,}"
Bu string operasyonlarını öğrendiğinde sed ve awk kullanma ihtiyacını ciddi ölçüde azaltırsın. Dosya yolu ayrıştırma, log analizi gibi işlemlerde doğrudan Bash string operatörleri çok daha hızlı çalışır.
Diziler: Bash’in Gücünü Kullanmak
Diziler, birden fazla değeri tek bir değişkende saklamanı sağlar. Bash’te iki tür dizi var: indeksli diziler ve ilişkisel diziler (associative arrays).
İndeksli Diziler
#!/bin/bash
# Dizi tanımlama
sunucular=("web01" "web02" "db01" "cache01")
# Eleman ekleme
sunucular+=("backup01")
# Tek elemana erişim
echo "İlk sunucu: ${sunucular[0]}"
echo "Üçüncü sunucu: ${sunucular[2]}"
# Tüm elemanlar
echo "Tüm sunucular: ${sunucular[@]}"
# Dizi uzunluğu
echo "Sunucu sayısı: ${#sunucular[@]}"
# Dizi üzerinde döngü
for sunucu in "${sunucular[@]}"; do
echo "Kontrol ediliyor: $sunucu"
# ping -c 1 "$sunucu" > /dev/null 2>&1 && echo "$sunucu erişilebilir" || echo "$sunucu erişilemiyor"
done
# Belirli index aralığı
echo "2. ve 3. sunucular: ${sunucular[@]:1:2}"
# Bir elemanı silme
unset sunucular[1]
echo "web02 silindi: ${sunucular[@]}"
İlişkisel Diziler (Hash Maps)
Bash 4 ile gelen ilişkisel diziler, anahtar-değer çiftleri saklamak için mükemmeldir. Gerçek hayatta sunucu-IP eşleştirmesi, servis-port eşleştirmesi gibi durumlarda çok işe yarar.
#!/bin/bash
# İlişkisel dizi MUTLAKA declare ile tanımlanmalı
declare -A sunucu_ip
sunucu_ip["web01"]="192.168.1.10"
sunucu_ip["web02"]="192.168.1.11"
sunucu_ip["db01"]="192.168.1.20"
sunucu_ip["cache01"]="192.168.1.30"
# Değere erişim
echo "web01 IP: ${sunucu_ip["web01"]}"
# Tüm anahtarlar
echo "Sunucular: ${!sunucu_ip[@]}"
# Tüm değerler
echo "IP Adresleri: ${sunucu_ip[@]}"
# Döngü ile gezme
for sunucu in "${!sunucu_ip[@]}"; do
echo "$sunucu -> ${sunucu_ip[$sunucu]}"
done
# Anahtar var mı kontrolü
if [[ -v sunucu_ip["web03"] ]]; then
echo "web03 tanımlı"
else
echo "web03 tanımlı değil"
fi
Özel Değişkenler ve Parametreler
Bash’in built-in özel değişkenlerini bilmek script yazımını çok kolaylaştırır.
- $0: Scriptin kendisinin adı
- $1, $2, …$9: Komut satırı parametreleri
- $@: Tüm parametreler (ayrı ayrı)
- $*: Tüm parametreler (tek string)
- $#: Parametre sayısı
- $?: Son komutun çıkış kodu
- $$: Mevcut process’in PID’i
- $!: Son arka plan process’in PID’i
- $_: Son komutun son argümanı
#!/bin/bash
# yedek_al.sh - Gerçek dünya kullanım örneği
SCRIPT_ADI=$(basename "$0")
if [[ $# -lt 2 ]]; then
echo "Kullanım: $SCRIPT_ADI <kaynak_dizin> <hedef_dizin> [etiket]"
echo "Örnek: $SCRIPT_ADI /var/www/html /backup/web gunluk"
exit 1
fi
KAYNAK="$1"
HEDEF="$2"
ETIKET="${3:-varsayilan}" # Varsayılan değer atama
TARIH=$(date +%Y%m%d_%H%M%S)
PID_DOSYASI="/tmp/${SCRIPT_ADI}.pid"
echo "Script: $SCRIPT_ADI"
echo "PID: $$"
echo "Kaynak: $KAYNAK"
echo "Hedef: $HEDEF"
echo "Etiket: $ETIKET"
# PID dosyası oluştur
echo $$ > "$PID_DOSYASI"
tar -czf "${HEDEF}/${ETIKET}_${TARIH}.tar.gz" "$KAYNAK"
if [[ $? -eq 0 ]]; then
echo "Yedekleme başarılı!"
else
echo "Yedekleme başarısız! Çıkış kodu: $?"
exit 1
fi
rm -f "$PID_DOSYASI"
Varsayılan Değer Atamaları
Bu operatörler production scriptlerinde güvenli kod yazmanın temelini oluşturur.
#!/bin/bash
# ${degisken:-varsayilan}: Değişken boşsa varsayılanı kullan
ORTAM="${ORTAM:-production}"
# ${degisken:=varsayilan}: Değişken boşsa hem kullan hem ata
: "${LOG_SEVIYE:=INFO}"
# ${degisken:?hata_mesaji}: Değişken boşsa hata ver ve çık
DB_SUNUCU="${DB_SUNUCU:?'DB_SUNUCU ortam değişkeni tanımlanmamış!'}"
# ${degisken:+alternatif}: Değişken doluysa alternatifi kullan
DEBUG_FLAG="${DEBUG:+--verbose}"
echo "rsync $DEBUG_FLAG /kaynak /hedef"
Gerçek Dünya Senaryosu: Log Analiz Scripti
Buraya kadar öğrendiklerini bir araya getiren pratik bir örnek yapalım. Bu script, Nginx access loglarını analiz eder ve en çok istek yapan IP’leri raporlar.
#!/bin/bash
# log_analiz.sh - Nginx access log analizi
LOG_DOSYASI="${1:-/var/log/nginx/access.log}"
LIMIT="${2:-10}"
RAPOR_DOSYASI="/tmp/log_rapor_$(date +%Y%m%d).txt"
# Kontroller
if [[ ! -f "$LOG_DOSYASI" ]]; then
echo "HATA: Log dosyası bulunamadı: $LOG_DOSYASI" >&2
exit 1
fi
declare -A ip_sayac
declare -A durum_sayac
toplam_istek=0
echo "Log analizi başlıyor: $LOG_DOSYASI"
echo "---"
# Log dosyasını satır satır oku
while IFS= read -r satir; do
# IP adresini çıkar (ilk alan)
ip=$(echo "$satir" | awk '{print $1}')
# HTTP durum kodunu çıkar
durum=$(echo "$satir" | awk '{print $9}')
# Sayaçları artır
if [[ -n "$ip" ]]; then
((ip_sayac["$ip"]++))
toplam_istek=$((toplam_istek + 1))
fi
if [[ "$durum" =~ ^[0-9]+$ ]]; then
((durum_sayac["$durum"]++))
fi
done < "$LOG_DOSYASI"
# Rapor oluştur
{
echo "=== Log Analiz Raporu ==="
echo "Tarih: $(date)"
echo "Dosya: $LOG_DOSYASI"
echo "Toplam istek: $toplam_istek"
echo ""
echo "--- En Fazla İstek Yapan $LIMIT IP ---"
# İlişkisel diziyi sırala
for ip in "${!ip_sayac[@]}"; do
echo "${ip_sayac[$ip]} $ip"
done | sort -rn | head -n "$LIMIT"
echo ""
echo "--- HTTP Durum Kodu Dağılımı ---"
for kod in "${!durum_sayac[@]}"; do
echo " $kod: ${durum_sayac[$kod]} istek"
done | sort
} | tee "$RAPOR_DOSYASI"
echo ""
echo "Rapor kaydedildi: $RAPOR_DOSYASI"
Bu scripti çalıştırmak için:
chmod +x log_analiz.sh
./log_analiz.sh /var/log/nginx/access.log 20
Readonly ve Sabitler
Script içinde değiştirilmemesi gereken değişkenler için readonly kullan.
#!/bin/bash
# Sabitler
readonly VERSIYON="2.1.0"
readonly KONFIG_DIZIN="/etc/uygulamam"
readonly LOG_DIZIN="/var/log/uygulamam"
readonly MAX_DENEME=3
# declare ile de yapılabilir
declare -r TIMEOUT=30
echo "Uygulama v${VERSIYON} başlatılıyor"
echo "Konfigürasyon: $KONFIG_DIZIN"
# Readonly değişkeni değiştirmeye çalışmak hata verir
# VERSIYON="3.0.0" # bash: VERSIYON: readonly variable
Nameref: Değişken Referansları
Bash 4.3 ile gelen nameref özelliği, değişken adını dinamik olarak referans almanı sağlar. Büyük scriptlerde fonksiyon yazarken çok işe yarar.
#!/bin/bash
# nameref kullanımı
guncelle_sayac() {
local -n _sayac="$1" # nameref: $1'deki isme referans
((_sayac++))
}
basarili=0
basarisiz=0
for sunucu in web01 web02 web03; do
# Simüle edilmiş bağlantı kontrolü
if ping -c 1 -W 1 "$sunucu" > /dev/null 2>&1; then
guncelle_sayac basarili
else
guncelle_sayac basarisiz
fi
done
echo "Başarılı: $basarili"
echo "Başarısız: $basarisiz"
Sık Yapılan Hatalar ve Çözümleri
Yıllar içinde gördüğüm en yaygın hataları ve çözümlerini listeleyelim.
Boşluklu değişken kullanımı: Değişkenleri her zaman çift tırnak içinde kullan, aksi halde boşluk içeren değerlerde beklenmedik davranışlar yaşarsın.
#!/bin/bash
DIZIN="/var/log/my app" # Boşluk içeren path
# YANLIŞ
ls $DIZIN # /var/log/my ve app olarak iki ayrı argüman
# DOĞRU
ls "$DIZIN" # Tek bir argüman olarak işlenir
# Dizi elemanlarında da aynı kural geçerli
dosyalar=("dosya 1.txt" "dosya 2.txt" "normal.txt")
# YANLIŞ
for f in ${dosyalar[@]}; do echo "$f"; done
# DOĞRU
for f in "${dosyalar[@]}"; do echo "$f"; done
Silinmiş dizi indexleri sonrası döngü: Diziden eleman sildiğinde index’ler sıfırlanmaz. Bu durumu göz önünde bulundur.
#!/bin/bash
liste=(a b c d e)
unset liste[2] # c silindi
echo "Uzunluk: ${#liste[@]}" # 4 (doğru)
echo "Elemanlar: ${liste[@]}" # a b d e (doğru)
echo "Index 2: '${liste[2]}'" # boş! (dikkat)
# Güvenli döngü
for eleman in "${liste[@]}"; do
echo "$eleman"
done
Sonuç
Bash’te değişkenler ve diziler ilk bakışta karmaşık görünse de birkaç temel kuralı içselleştirince her şey yerine oturuyor. Özet olarak aklında tutman gerekenler şunlar:
- Değişkenleri her zaman çift tırnak içinde kullan
- Fonksiyonlarda değişkenleri
localile tanımla - İlişkisel diziler için
declare -Azorunlu - Sabitler için
readonlykullan - Tip garantisi istiyorsan
declare -iile integer tanımla - Ondalıklı hesaplama için
bckullan - Süslü parantez kullanımını alışkanlık haline getir
Bu yapıları kavramak, daha karmaşık otomasyon scriptleri yazmanın temelini oluşturuyor. Sonraki adım olarak fonksiyon yazımı ve hata yönetimi konularına geçmeni öneririm. Gerçek üretim ortamında çalışan bir script yazmadan önce mutlaka shellcheck ile kodunu analiz et; bu araç, değişken kullanımıyla ilgili pek çok potansiyel hatayı önceden yakalar.