Bash Script Nedir: Temel Yapı ve İlk Script

Terminale her gün onlarca komut yazıyorsun, aynı işlemleri tekrar tekrar yapıyorsun ve bir noktada “bunu otomatikleştiremez miyim?” diye soruyorsun kendine. İşte tam bu noktada bash scripting devreye giriyor. Bash scriptleri, komut satırında yaptığın her şeyi bir dosyaya yazıp tek seferde çalıştırmanı sağlayan güçlü araçlar. Bu yazıda sıfırdan başlayarak bash scriptlerinin ne olduğunu, nasıl çalıştığını ve gerçek dünyada nasıl kullanabileceğini öğreneceğiz.

Bash Script Nedir?

Bash script, temel olarak bir metin dosyasına yazılmış komutlar topluluğudur. Bash (Bourne Again Shell), Linux ve macOS sistemlerde varsayılan shell olarak kullanılan yorumlayıcıdır. Terminale tek tek yazabileceğin komutları bir dosyaya toplayıp, bu dosyayı çalıştırarak tüm komutların sırayla işlenmesini sağlarsın.

Mesela her sabah sunucuna bağlandığında şu işlemleri yapıyorsun:

  • Disk kullanımını kontrol et
  • Bellek durumunu gör
  • Çalışan servisleri listele
  • Log dosyalarını kontrol et

Bu dört işlemi her seferinde ayrı ayrı yazmak yerine, bir script dosyasına yazıp tek komutla çalıştırabilirsin. İşte bash scripting’in özü bu kadar basit.

Shebang Satırı: Her Şeyin Başlangıcı

Bir bash scriptinin ilk satırı her zaman shebang ile başlar. Bu satır, işletim sistemine dosyayı hangi yorumlayıcıyla çalıştırması gerektiğini söyler.

#!/bin/bash

#! karakterleri shebang olarak adlandırılır ve arkasından gelen yol, kullanılacak yorumlayıcıyı belirtir. Bazı sistemlerde bash’in farklı konumlarda olabileceğini göz önünde bulundurarak şu alternatifi de kullanabilirsin:

#!/usr/bin/env bash

Bu yaklaşım daha taşınabilir çünkü env komutu bash’i sistem PATH’inden bulur. Production ortamlarında ve farklı sistemlerde çalışacak scriptlerde bu yöntemi tercih etmeni tavsiye ederim.

İlk Bash Scriptini Yazalım

Hadi klasik “Merhaba Dünya” ile başlayalım ama biraz daha işe yarar hale getirelim:

#!/bin/bash

# İlk scriptim - Sistem bilgisi göster
echo "Merhaba, $(whoami)!"
echo "Bugün: $(date '+%d/%m/%Y %H:%M')"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"

Bu scripti oluşturmak için:

nano ilk_script.sh
# veya
vim ilk_script.sh

Dosyayı kaydettikten sonra çalıştırma izni ver:

chmod +x ilk_script.sh

Ve çalıştır:

./ilk_script.sh

Çıktı şöyle görünecek:

Merhaba, ahmet!
Bugün: 15/01/2025 09:30
Hostname: web-server-01
Kernel: 5.15.0-91-generic

Değişkenler

Bash scriptlerinde değişkenler tanımlarken = işaretinin iki yanında boşluk bırakmamalısın. Bu en sık yapılan hatalardan biri.

#!/bin/bash

# Değişken tanımlama
ISIM="Ahmet"
YAS=30
SUNUCU_ADI=$(hostname)
TARIH=$(date '+%Y-%m-%d')

# Değişken kullanma - $ ile erişilir
echo "Kullanıcı: $ISIM"
echo "Yaş: $YAS"
echo "Sunucu: $SUNUCU_ADI"
echo "Tarih: $TARIH"

# Çift tırnak içinde değişken çalışır
echo "Bugün $TARIH tarihinde $SUNUCU_ADI üzerindeyiz"

# Tek tırnak içinde değişken çalışmaz, literal alınır
echo 'Bu $ISIM değişken değil, literal metin'

Değişken isimlendirme kuralları:

  • Büyük harfle yazmak zorunlu değil ama convention olarak global değişkenler büyük harfle yazılır
  • Rakamla başlayamaz
  • Boşluk içeremez
  • Özel karakterler kullanılamaz (underscore hariç)

Özel Değişkenler

Bash’in kendi tanımladığı bazı özel değişkenler var ve bunları mutlaka bilmen lazım:

#!/bin/bash

echo "Script adı: $0"
echo "İlk argüman: $1"
echo "İkinci argüman: $2"
echo "Tüm argümanlar: $@"
echo "Argüman sayısı: $#"
echo "Son komutun çıkış kodu: $?"
echo "Script'in PID'i: $$"

Bu scripti şöyle çalıştırırsın:

./script.sh parametre1 parametre2

$0: Script’in kendi adı $1, $2, $3…: Sırayla argümanlar $@: Tüm argümanları liste olarak verir $#: Kaç argüman verildiğini sayar $?: Son çalıştırılan komutun başarı/hata kodu (0 başarı, sıfırdan farklı hata) $$: Çalışan script’in process ID’si

Koşullu İfadeler (if-else)

Gerçek dünya scriptlerinde karar verme mekanizması şart. Bir log dosyası var mı, bir servis çalışıyor mu, disk doldu mu gibi kontroller yapacaksın.

#!/bin/bash

DISK_KULLANIM=$(df / | awk 'NR==2 {print $5}' | tr -d '%')

if [ "$DISK_KULLANIM" -gt 90 ]; then
    echo "KRITIK: Disk %$DISK_KULLANIM dolu!"
    echo "Lütfen acilen müdahale edin."
elif [ "$DISK_KULLANIM" -gt 75 ]; then
    echo "UYARI: Disk %$DISK_KULLANIM dolu."
    echo "Takipte olun."
else
    echo "OK: Disk kullanımı normal (%$DISK_KULLANIM)"
fi

Karşılaştırma Operatörleri

Sayısal karşılaştırmalar için:

  • -eq: Eşit (equal)
  • -ne: Eşit değil (not equal)
  • -gt: Büyüktür (greater than)
  • -lt: Küçüktür (less than)
  • -ge: Büyük eşit (greater or equal)
  • -le: Küçük eşit (less or equal)

String karşılaştırmaları için:

  • = veya ==: Eşit
  • !=: Eşit değil
  • -z: String boş mu?
  • -n: String dolu mu?

Dosya kontrolleri için:

  • -f: Düzenli dosya mı?
  • -d: Dizin mi?
  • -e: Var mı? (dosya veya dizin)
  • -r: Okunabilir mi?
  • -w: Yazılabilir mi?
  • -x: Çalıştırılabilir mi?

Döngüler

Döngüler, aynı işlemi birden fazla kez yapman gerektiğinde hayat kurtarır. Yüzlerce dosyayı işlemek, birden fazla sunucuya bağlanmak, bir listedeki her öğeyle bir şey yapmak gibi senaryolarda kullanırsın.

For Döngüsü

#!/bin/bash

# Basit for döngüsü
SUNUCULAR="web-01 web-02 web-03 db-01"

for SUNUCU in $SUNUCULAR; do
    echo "Kontrol ediliyor: $SUNUCU"
    ping -c 1 -W 2 "$SUNUCU" > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo "  $SUNUCU - ERIŞILEBILIR"
    else
        echo "  $SUNUCU - ERIŞILEMIYOR!"
    fi
done

echo "Kontrol tamamlandı."

While Döngüsü

#!/bin/bash

# Log dosyasını satır satır oku
LOG_DOSYA="/var/log/auth.log"
SAYAC=0

while IFS= read -r SATIR; do
    if echo "$SATIR" | grep -q "Failed password"; then
        SAYAC=$((SAYAC + 1))
    fi
done < "$LOG_DOSYA"

echo "Başarısız giriş denemesi sayısı: $SAYAC"

Fonksiyonlar

Bir kod bloğunu birden fazla yerde kullanacaksan, fonksiyon yaz. Bu hem kodu temiz tutar hem de bakımını kolaylaştırır.

#!/bin/bash

# Fonksiyon tanımlama
log_yaz() {
    local MESAJ="$1"
    local TIP="${2:-INFO}"
    local ZAMAN=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$ZAMAN] [$TIP] $MESAJ"
}

servis_kontrol() {
    local SERVIS="$1"
    if systemctl is-active --quiet "$SERVIS"; then
        log_yaz "$SERVIS servisi çalışıyor" "OK"
        return 0
    else
        log_yaz "$SERVIS servisi çalışmıyor!" "HATA"
        return 1
    fi
}

# Fonksiyonları kullanalım
log_yaz "Script başlatıldı"

SERVISLER="nginx mysql ssh"
for SERVIS in $SERVISLER; do
    servis_kontrol "$SERVIS"
done

log_yaz "Script tamamlandı"

local anahtar kelimesi, değişkenin sadece o fonksiyon içinde geçerli olmasını sağlar. Bunu kullanmazsan değişken global scope’a çıkar ve beklenmedik sorunlara yol açabilir.

Gerçek Dünya Senaryosu: Yedekleme Scripti

Şimdiye kadar öğrendiklerimizi birleştirerek gerçekten işe yarar bir script yazalım. Bu script belirtilen dizini yedekler, eski yedekleri temizler ve işlemi loglar.

#!/bin/bash

# ========================================
# Basit Yedekleme Scripti
# Kullanım: ./yedekle.sh /yedeklenecek/dizin
# ========================================

# Yapılandırma
YEDEK_HEDEF="/backup"
YEDEK_SAKLA=7  # Kaç günlük yedek tutulsun
LOG_DOSYA="/var/log/yedekleme.log"

# Fonksiyonlar
log() {
    local MESAJ="$1"
    local ZAMAN=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$ZAMAN] $MESAJ" | tee -a "$LOG_DOSYA"
}

hata_cik() {
    log "HATA: $1"
    exit 1
}

# Argüman kontrolü
if [ $# -eq 0 ]; then
    echo "Kullanım: $0 /yedeklenecek/dizin"
    exit 1
fi

KAYNAK="$1"

# Kaynak dizin var mı?
if [ ! -d "$KAYNAK" ]; then
    hata_cik "Kaynak dizin bulunamadı: $KAYNAK"
fi

# Hedef dizin yoksa oluştur
if [ ! -d "$YEDEK_HEDEF" ]; then
    mkdir -p "$YEDEK_HEDEF" || hata_cik "Hedef dizin oluşturulamadı"
fi

# Yedek dosya adı oluştur
DIZIN_ADI=$(basename "$KAYNAK")
TARIH=$(date '+%Y%m%d_%H%M%S')
YEDEK_DOSYA="$YEDEK_HEDEF/${DIZIN_ADI}_${TARIH}.tar.gz"

log "Yedekleme başlıyor: $KAYNAK"

# Yedek al
tar -czf "$YEDEK_DOSYA" -C "$(dirname "$KAYNAK")" "$DIZIN_ADI" 2>/dev/null
if [ $? -ne 0 ]; then
    hata_cik "Yedekleme başarısız oldu"
fi

BOYUT=$(du -sh "$YEDEK_DOSYA" | cut -f1)
log "Yedek oluşturuldu: $YEDEK_DOSYA ($BOYUT)"

# Eski yedekleri temizle
log "Eski yedekler temizleniyor ($YEDEK_SAKLA günden eski)..."
SILINEN=$(find "$YEDEK_HEDEF" -name "${DIZIN_ADI}_*.tar.gz" -mtime +"$YEDEK_SAKLA" -delete -print | wc -l)
log "$SILINEN eski yedek silindi"

log "Yedekleme tamamlandı"

Bu scripti crontab’a ekleyerek otomatik çalıştırabilirsin:

# Her gece saat 02:00'de çalışsın
0 2 * * * /scripts/yedekle.sh /var/www/html

Script Yazarken Dikkat Edilmesi Gerekenler

Set Komutları ile Güvenli Scripting

#!/bin/bash
set -e  # Hata olursa scripti durdur
set -u  # Tanımsız değişken kullanılırsa hata ver
set -o pipefail  # Pipe'daki hataları yakala

Veya kısaca:

set -euo pipefail

Bu üç ayarı production scriptlerinde mutlaka kullan. Özellikle set -e olmadan script bir komut hata verse bile çalışmaya devam eder ve farkında olmadan zarar verebilirsin.

Hata Yakalama

#!/bin/bash
set -e

temizlik() {
    echo "Script sonlandı, temizlik yapılıyor..."
    # Geçici dosyaları sil
    rm -f /tmp/script_gecici_*
}

# Script herhangi bir şekilde sonlanırsa temizlik çalışsın
trap temizlik EXIT

# Hata durumunda özel mesaj
trap 'echo "HATA: Satır $LINENO da sorun oluştu"' ERR

echo "Script çalışıyor..."
# Script içeriği buraya

trap komutu, belirli sinyaller veya durumlar gerçekleştiğinde çalışacak fonksiyonu tanımlar. EXIT her zaman, ERR hata olduğunda, INT Ctrl+C’ye basıldığında tetiklenir.

Script’e Renk Katmak

Terminaldeki çıktıları okunabilir hale getirmek için renk kodları kullanabilirsin:

#!/bin/bash

# Renk tanımlamaları
KIRMIZI='33[0;31m'
YESIL='33[0;32m'
SARI='33[1;33m'
MAVI='33[0;34m'
SIFIRLA='33[0m'

# Renkli çıktı fonksiyonları
basarili() { echo -e "${YESIL}[OK]${SIFIRLA} $1"; }
uyari()    { echo -e "${SARI}[UYARI]${SIFIRLA} $1"; }
hata()     { echo -e "${KIRMIZI}[HATA]${SIFIRLA} $1"; }
bilgi()    { echo -e "${MAVI}[INFO]${SIFIRLA} $1"; }

# Kullanım
bilgi "Sistem kontrolü başlıyor..."
basarili "Nginx servisi çalışıyor"
uyari "Disk kullanımı %78 seviyesinde"
hata "MySQL servisi çalışmıyor!"

Script’leri Nereye Koymalısın?

Kişisel kullandığın scriptler için:

  • ~/bin/ dizini oluştur ve PATH’e ekle
  • ~/.local/bin/ dizinini kullan (bazı distrolarda otomatik PATH’te)

Sistem geneli scriptler için:

  • /usr/local/bin/ – tüm kullanıcılara açık
  • /opt/scripts/ – kurumsal ortamlarda yaygın
  • /etc/cron.d/ – sadece cron görevleri için

PATH’e kişisel bin dizini eklemek için ~/.bashrc veya ~/.bash_profile dosyasına şunu ekle:

export PATH="$HOME/bin:$PATH"

Scripti Debug Etmek

Bir şeyler yanlış gidince ne yapacaksın?

# Script'i debug modunda çalıştır, her komutu gösterir
bash -x ./script.sh

# Sadece belirli bir bölümü debug et
set -x  # Debug başlat
# ... kodlar ...
set +x  # Debug bitir

-x flag’i, her komutun çalıştırılmadan önce ekrana yazılmasını sağlar. Hangi değerin atandığını, hangi dalın çalıştığını görmek için paha biçilmez bir araç.

Sonuç

Bash scripting, bir sysadmin olarak en çok değer kazandığın becerilerden biri. Bugün öğrendiklerimizi özetleyelim:

  • Shebang satırı ile her script başlar ve yorumlayıcıyı tanımlar
  • Değişkenler ile veri saklarsın, $ ile erişirsin
  • if-else ile karar mekanizması kurarsın
  • for ve while döngüleriyle tekrarlı işleri otomatikleştirirsin
  • Fonksiyonlar ile kodu modüler ve yeniden kullanılabilir yaparsın
  • set -euo pipefail ile güvenli scripting yaparsın
  • trap ile temizlik ve hata yönetimi sağlarsın

Bundan sonraki adım ne? Küçük bir şeyle başla. Her gün terminalde tekrar ettiğin bir görevi al ve onu script haline getir. Disk kontrolü, log temizliği, servis health check, ne olursa. Yazdıkça gelişirsin. Bash scripting öğrenmek için en iyi yol bol bol yazmak ve hataları incelemek.

Bir sonraki yazıda bu temelleri genişleterek dizi (array) kullanımını, string manipülasyonu ve daha gelişmiş kontrol yapılarını ele alacağız. O yazıya hazır olmak için bugün anlattığım temel yapıları kendi sisteminde denemeni tavsiye ederim.

Yorum yapın