Bash scripting öğrenirken bir noktadan sonra temel komutlar yetmez hale gelir. Pipe’lar ve yönlendirmeler güzel, ama bazı senaryolarda daha fazlasına ihtiyacın olur. İşte tam bu noktada process substitution ve here-doc devreye girer. Bu iki özellik, script’lerini gerçekten güçlü kılan, “evet bunu bilen yazmış” dedirten yapılardır. Bugün bu konuyu derinlemesine inceleyeceğiz, gerçek dünya örnekleriyle pekiştireceğiz.
Here-Doc Nedir ve Neden Kullanıyoruz?
Here-doc (heredoc), yani “here document”, bir komuta birden fazla satır girdi sağlamanın en temiz yoludur. Normalde bir komuta girdi verirken ya dosyadan okursun ya da echo ile tek satır geçersin. Ama ya 20 satırlık bir SQL sorgusu çalıştırman gerekiyorsa? Ya da bir config dosyası oluşturacaksan? İşte heredoc tam burada hayat kurtarır.
Temel sözdizimi şöyle:
komut << ETIKET
içerik satırı 1
içerik satırı 2
ETIKET
Buradaki ETIKET istediğin herhangi bir kelime olabilir. Geleneksel olarak EOF (End Of File) veya EOL kullanılır, ama bu zorunlu değil. Tek kural: kapanış etiketi satırın en başında, önünde hiçbir boşluk olmadan bulunmalıdır.
Basit Bir Heredoc Örneği
#!/bin/bash
# Kullanıcıya bilgi mesajı gönderme
mail -s "Sunucu Bakım Bildirimi" [email protected] << EOF
Merhaba,
Bu gece 02:00 - 04:00 arasında sunucularımızda bakım yapılacaktır.
Etkilenecek sistemler:
- web-01
- web-02
- db-master
Sorularınız için [email protected] adresine yazabilirsiniz.
Sistem Yönetimi Ekibi
EOF
Bu kadar basit. Artık echo ile satır satır uğraşmak yok, tek bir blokta tüm içeriği veriyorsun.
Heredoc ile Değişken Genişletme
Heredoc’un güçlü taraflarından biri, içinde Bash değişkenlerini, komut çıktılarını ve hatta aritmetik ifadeleri kullanabilmektir.
#!/bin/bash
SUNUCU_ADI=$(hostname)
TARIH=$(date '+%Y-%m-%d %H:%M')
DISK_KULLANIM=$(df -h / | awk 'NR==2 {print $5}')
ADMIN_MAIL="[email protected]"
cat << EOF
=== Günlük Sistem Raporu ===
Sunucu: $SUNUCU_ADI
Tarih: $TARIH
Root Disk Kullanımı: $DISK_KULLANIM
Bu rapor otomatik olarak oluşturulmuştur.
EOF
Script çalıştığında değişkenler otomatik olarak genişletilir. Çıktı şöyle görünür:
=== Günlük Sistem Raporu ===
Sunucu: web-01.sirket.com
Tarih: 2024-01-15 08:30
Root Disk Kullanımı: 67%
Bu rapor otomatik olarak oluşturulmuştur.
Değişken Genişletmeyi Kapatmak: Tırnaklı Heredoc
Bazen içeriğin olduğu gibi kalmasını isteyebilirsin. Mesela bir Bash script şablonu oluşturuyorsun ve $değişken ifadelerinin genişletilmesini istemiyorsun. Bu durumda etiketi tek tırnak içine alırsın:
#!/bin/bash
# Yeni bir monitoring script şablonu oluşturuyor
cat << 'EOF' > /opt/scripts/monitor_template.sh
#!/bin/bash
# Bu script otomatik oluşturulmuştur
HOSTNAME=$(hostname)
DATE=$(date)
THRESHOLD=${1:-80}
if [ $(df -h / | awk 'NR==2 {print $5}' | tr -d '%') -gt $THRESHOLD ]; then
echo "UYARI: $HOSTNAME üzerinde disk kullanımı %$THRESHOLD limitini aştı!"
fi
EOF
chmod +x /opt/scripts/monitor_template.sh
echo "Şablon oluşturuldu."
Burada 'EOF' şeklinde tırnak kullandığımız için içindeki $HOSTNAME, $DATE gibi ifadeler genişletilmedi. Dosyaya olduğu gibi yazıldı. Bu özellik özellikle script üretecek script’ler yazarken çok işe yarıyor.
Girintili Heredoc: <<-
Büyük script’lerde kod okunabilirliği önemlidir. Normal heredoc’ta kapanış etiketi mutlaka satır başında olmak zorunda, bu da girintili kod yazarken çirkin durumlar ortaya çıkarır. <<- operatörü bu sorunu çözer: kapanış etiketinin ve içeriğin başındaki tab karakterlerini yok sayar.
#!/bin/bash
deploy_config() {
local APP_NAME=$1
local PORT=$2
local ENV=$3
cat <<- EOF > /etc/nginx/conf.d/${APP_NAME}.conf
server {
listen $PORT;
server_name ${APP_NAME}.sirket.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
access_log /var/log/nginx/${APP_NAME}_${ENV}.log;
}
EOF
echo "$APP_NAME için nginx config oluşturuldu."
}
deploy_config "myapp" "443" "production"
Dikkat: <<- sadece tab karakterlerini siler, boşlukları değil. Editörünüzde tab kullandığınızdan emin olun.
Heredoc ile Veritabanı İşlemleri
Sysadmin’lerin en çok kullandığı yerlerden biri veritabanı yönetimidir. MySQL/PostgreSQL ile heredoc kombinasyonu inanılmaz temiz bir kullanım sunar:
#!/bin/bash
DB_HOST="localhost"
DB_USER="admin"
DB_PASS="gizli_sifre"
DB_NAME="uygulama_db"
YEDEK_TARIH=$(date '+%Y%m%d')
# Günlük temizlik ve istatistik scripti
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" << EOF
-- 30 günden eski log kayıtlarını temizle
DELETE FROM sistem_loglari
WHERE olusturma_tarihi < DATE_SUB(NOW(), INTERVAL 30 DAY);
-- Temizlenen kayıt sayısını göster
SELECT ROW_COUNT() as silinen_kayit_sayisi;
-- Tablo istatistiklerini güncelle
ANALYZE TABLE sistem_loglari;
ANALYZE TABLE kullanici_olaylari;
-- Mevcut tablo boyutlarını raporla
SELECT
table_name as tablo,
ROUND(((data_length + index_length) / 1024 / 1024), 2) as boyut_mb
FROM information_schema.TABLES
WHERE table_schema = '$DB_NAME'
ORDER BY boyut_mb DESC;
EOF
echo "Veritabanı bakımı tamamlandı: $YEDEK_TARIH"
Bu script’i cron’a ekleyip her gece çalıştırabilirsin. Hem temizlik hem de raporlama tek script’te.
Process Substitution Nedir?
Process substitution, bir komutun çıktısını sanki bir dosyaymış gibi başka bir komuta verebilmeni sağlar. Sözdizimi <(komut) veya >(komut) şeklindedir.
Neden bu gerekli? Pipe’lar güçlüdür ama tek yönlüdür: komut1 | komut2. Eğer iki komutun çıktısını karşılaştırmak ya da aynı anda iki farklı kaynaktan okumak istersen, pipe yetmez. Process substitution burada devreye girer.
Basit bir örnek: İki dizinin içeriğini karşılaştırma.
#!/bin/bash
# İki sunucudaki kurulu paketleri karşılaştır
# Normalde bunu yapamazsın:
# diff "ssh web-01 dpkg -l" "ssh web-02 dpkg -l"
# Process substitution ile:
diff <(ssh web-01 dpkg -l | awk '{print $2}' | sort)
<(ssh web-02 dpkg -l | awk '{print $2}' | sort)
# Çıktı: Hangi paketler bir sunucuda var diğerinde yok
Bu örnekte <(...) ifadesi Bash’e şunu söylüyor: “Bu komutu çalıştır, çıktısını geçici bir dosyaya (aslında named pipe veya /dev/fd) yaz ve o dosyanın yolunu bana ver.” diff komutu ise iki normal dosya aldığını sanıyor.
Process Substitution Gerçek Kullanım Senaryoları
Log Analizi ve Karşılaştırma
Sysadmin olarak en sık yaptığım şeylerden biri: iki farklı kaynaktan gelen verileri karşılaştırmak.
#!/bin/bash
# Aktif bağlantıları DNS ile doğrula
# Firewall'daki izin verilen IP'ler ile aktif bağlantıları karşılaştır
FIREWALL_IPS="/etc/firewall/allowed_ips.txt"
echo "=== Yetkisiz Bağlantı Kontrolü ==="
comm -23
<(ss -tn state established | awk 'NR>1 {print $5}' | cut -d: -f1 | sort -u)
<(sort "$FIREWALL_IPS")
# comm -23: sadece ilk dosyada olup ikincide olmayanları göster
# Yani: aktif bağlantılarda var ama firewall listesinde yok = şüpheli!
Backup Doğrulama Script’i
#!/bin/bash
KAYNAK_DIR="/var/www/html"
YEDEK_DIR="/backup/web"
LOG_FILE="/var/log/backup_verify.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Yedek doğrulama başlıyor..."
# Kaynak ve yedekte hangi dosyalar var karşılaştır
FARKLI_DOSYALAR=$(diff
<(find "$KAYNAK_DIR" -type f -exec md5sum {} ; | sort)
<(find "$YEDEK_DIR" -type f -exec md5sum {} ; | sed "s|$YEDEK_DIR|$KAYNAK_DIR|" | sort)
| grep "^[<>]" | wc -l)
if [ "$FARKLI_DOSYALAR" -eq 0 ]; then
log "BAŞARILI: Tüm dosyalar doğrulandı, yedek bütünlüklü."
else
log "UYARI: $FARKLI_DOSYALAR dosya farklı veya eksik!"
# Hangi dosyalar farklı, detaylı göster
diff
<(find "$KAYNAK_DIR" -type f | sort)
<(find "$YEDEK_DIR" -type f | sed "s|$YEDEK_DIR|$KAYNAK_DIR|" | sort)
| grep "^[<>]" >> "$LOG_FILE"
fi
Çıktı Yönlendirme ile Process Substitution: >(komut)
>(komut) sözdizimi de son derece kullanışlı. Bir komutun çıktısını birden fazla hedefe aynı anda göndermek istediğinde harika çalışır:
#!/bin/bash
# Büyük bir log dosyasını işle:
# - Hata satırlarını hata.log'a yaz
# - Uyarı satırlarını uyari.log'a yaz
# - Tüm çıktıyı aynı zamanda ana log'a yaz
LOG_KAYNAK="/var/log/uygulama/app.log"
TARIH=$(date '+%Y%m%d')
cat "$LOG_KAYNAK" | tee
>(grep "ERROR" > "/var/log/analiz/${TARIH}_hatalar.log")
>(grep "WARN" > "/var/log/analiz/${TARIH}_uyarilar.log")
>(grep "CRITICAL" | mail -s "KRİTİK: Acil müdahale gerekli" [email protected])
> "/var/log/analiz/${TARIH}_tum.log"
echo "Log analizi tamamlandı."
echo "Hata sayısı: $(grep -c 'ERROR' /var/log/analiz/${TARIH}_hatalar.log)"
echo "Uyarı sayısı: $(grep -c 'WARN' /var/log/analiz/${TARIH}_uyarilar.log)"
Bu script tek bir cat ile dosyayı bir kez okuyarak dört farklı hedefe aynı anda veri gönderiyor. Performans açısından da verimli.
Process Substitution ile While Loop: Alt Shell Tuzağı
Bash’in en sinir bozucu davranışlarından biri şudur: pipe’ın sağ tarafı bir alt shell’de çalışır. Bu yüzden şu kod beklendiği gibi çalışmaz:
# YANLIŞ - değişken kalıcı olmaz
TOPLAM=0
cat /var/log/access.log | while read satir; do
TOPLAM=$((TOPLAM + 1))
done
echo "Toplam: $TOPLAM" # Her zaman 0 çıkar!
Process substitution ile bu sorunu çözebilirsin:
#!/bin/bash
# DOĞRU - process substitution ile ana shell'de çalışır
TOPLAM=0
HATA_SAYISI=0
UYARI_SAYISI=0
while IFS= read -r satir; do
TOPLAM=$((TOPLAM + 1))
if echo "$satir" | grep -q "ERROR"; then
HATA_SAYISI=$((HATA_SAYISI + 1))
elif echo "$satir" | grep -q "WARN"; then
UYARI_SAYISI=$((UYARI_SAYISI + 1))
fi
done < <(cat /var/log/uygulama/app.log)
# Dikkat: < <(komut) - bir boşluk var aralarında!
echo "Toplam satır: $TOPLAM"
echo "Hata: $HATA_SAYISI"
echo "Uyarı: $UYARI_SAYISI"
< <(komut) sözdizimi ilginç görünüyor ama mantıklı: birinci < dosyadan yönlendirme, ikincisi process substitution başlangıcı.
Heredoc ve Process Substitution Birlikte
Bu iki özelliği birleştirince gerçekten güçlü script’ler yazabilirsin. Gerçek bir senaryo: dinamik config üretip hemen doğrulama yapmak.
#!/bin/bash
# Nginx config üret ve syntax kontrolü yap
APP_NAME="${1:-myapp}"
UPSTREAM_PORT="${2:-8080}"
SSL_CERT="/etc/ssl/certs/${APP_NAME}.crt"
SSL_KEY="/etc/ssl/private/${APP_NAME}.key"
# Config'i geçici bir process olarak üret ve nginx'e doğrulat
nginx -t -c <(cat << EOF
worker_processes 1;
error_log /dev/stderr;
pid /tmp/nginx_test.pid;
events {
worker_connections 1024;
}
http {
upstream ${APP_NAME}_backend {
server 127.0.0.1:${UPSTREAM_PORT};
}
server {
listen 80;
server_name ${APP_NAME}.sirket.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name ${APP_NAME}.sirket.com;
ssl_certificate $SSL_CERT;
ssl_certificate_key $SSL_KEY;
location / {
proxy_pass http://${APP_NAME}_backend;
proxy_set_header Host $host;
}
}
}
EOF
)
if [ $? -eq 0 ]; then
echo "Config syntax doğru! Gerçek config dosyasına yazılıyor..."
# Gerçek dosyaya yaz
cat << EOF > "/etc/nginx/conf.d/${APP_NAME}.conf"
# ... aynı içerik ...
EOF
nginx -s reload
else
echo "HATA: Config syntax hatası var, değişiklik yapılmadı."
exit 1
fi
Çok Satırlı SSH Komutları
Uzak sunucularda karmaşık işlemler yaparken heredoc inanılmaz temiz bir kullanım sunar:
#!/bin/bash
# Birden fazla sunucuda deployment yap
SUNUCULAR=("web-01" "web-02" "web-03")
APP_VERSION="${1:-latest}"
DEPLOY_USER="deploy"
for SUNUCU in "${SUNUCULAR[@]}"; do
echo "=== $SUNUCU üzerinde deployment başlıyor ==="
ssh -T "${DEPLOY_USER}@${SUNUCU}" << ENDSSH
set -e
echo "[$(hostname)] Deployment başlıyor: $APP_VERSION"
cd /opt/uygulama
# Mevcut versiyonu yedekle
cp -r current "backup_$(date +%Y%m%d_%H%M%S)"
# Yeni versiyonu çek
git fetch origin
git checkout "$APP_VERSION"
# Bağımlılıkları güncelle
npm install --production 2>&1
# Uygulamayı yeniden başlat
systemctl restart uygulama.service
# Sağlık kontrolü
sleep 3
if systemctl is-active --quiet uygulama.service; then
echo "[$(hostname)] Deployment başarılı!"
else
echo "[$(hostname)] HATA: Servis başlamadı!"
exit 1
fi
ENDSSH
if [ $? -ne 0 ]; then
echo "HATA: $SUNUCU deployment başarısız! Deployment durduruluyor."
exit 1
fi
done
echo "Tüm sunucularda deployment tamamlandı."
SSH heredoc kullanırken dikkat: ENDSSH etiketini tırnak içine almadığında değişkenler lokal makinede genişletilir. Bu örnekte $APP_VERSION lokal değişken, $(hostname) ise uzak sunucuda çalışmasını istediğimiz için kaçış karakteri kullandık.
Hata Yakalama ve Güvenli Kullanım Önerileri
Production script’lerinde şunlara dikkat etmek gerekir:
Geçici dosya temizliği: Process substitution genellikle /dev/fd veya named pipe kullanır, ama bazı sistemlerde geçici dosya oluşturabilir. trap ile temizlik yap:
#!/bin/bash
set -euo pipefail
# Geçici dosyalar için trap
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Heredoc ile güvenli config üretimi
CONFIG_FILE="$TEMP_DIR/uygulama.conf"
cat << EOF > "$CONFIG_FILE"
# Otomatik oluşturuldu: $(date)
[database]
host = ${DB_HOST:-localhost}
port = ${DB_PORT:-5432}
name = ${DB_NAME:?DB_NAME tanımlı değil!}
[cache]
backend = redis
host = ${REDIS_HOST:-localhost}
ttl = 3600
EOF
# Config'i doğrula
if grep -q "^$" "$CONFIG_FILE" | head -1; then
echo "Config boş satır içeriyor, kontrol et."
fi
# Yerine koy
cp "$CONFIG_FILE" /etc/uygulama/config.ini
echo "Config güncellendi."
set -euo pipefail ile çalışırken process substitution içindeki hatalar her zaman yakalanmayabilir. Kritik komutlar için dönüş değerlerini açıkça kontrol et.
Performans İpuçları
Process substitution, named pipe kullandığı için büyük veri setlerinde pipe kadar verimlidir. Ama bazı durumlar vardır ki dikkatli olmak gerekir:
- Aynı komutu birden fazla process substitution’da kullanıyorsan ve komut yavaşsa, önce sonucu bir değişkene ya da geçici dosyaya kaydet.
diff <(uzun_komut1) <(uzun_komut2)yapısında her iki komut paralel çalışır, bu genellikle avantajdır.- Heredoc büyük bloklar için
echoveyaprintfdöngülerine göre çok daha hızlıdır, çünkü her satır için ayrı process fork edilmez.
Sonuç
Process substitution ve heredoc, Bash’in en az kullanılan ama en etkili özelliklerinden ikisidir. heredoc ile çok satırlı içerikleri temiz ve okunabilir şekilde yönetebilir, config dosyaları üretebilir, uzak sunucularda karmaşık komut dizileri çalıştırabilirsin. process substitution ile ise pipe’ların çözemediği problemleri, özellikle birden fazla komut çıktısını karşılaştırma veya paralel işleme durumlarını çözebilirsin.
Bu özellikleri öğrendikten sonra geriye bakıp “ben bunu nasıl yapmıyordum?” diyeceksin. Özellikle deployment script’leri, monitoring araçları ve log analiz script’leri yazdığında bu yapılar sana muazzam esneklik sağlar. Kod okunabilirliği de bir o kadar artar, üç ay sonra script’ine baktığında ne yaptığını anlayabilirsin, ki sysadmin dünyasında bu çoğu zaman en önemli kriter haline gelir.
Bir sonraki adım olarak bu örnekleri kendi ortamında denemenizi öneririm. Önce basit heredoc’larla başlayın, ardından process substitution’ı küçük karşılaştırma görevlerinde kullanın. Zamanla bu yapılar sezgisel hale gelir ve script yazmak çok daha keyifli bir hal alır.