Değişkenler, Diziler ve Veri Tipleri: Bash Scripting Rehberi

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 local ile tanımla
  • İlişkisel diziler için declare -A zorunlu
  • Sabitler için readonly kullan
  • Tip garantisi istiyorsan declare -i ile integer tanımla
  • Ondalıklı hesaplama için bc kullan
  • 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.

Yorum yapın