Process Substitution ve /dev/fd ile Dosyaları Sanal Olarak Boru Hattına Bağlama
Yıllarca shell scripting yapan biri olarak şunu rahatlıkla söyleyebilirim: çoğu sistem yöneticisi process substitution’ı ya hiç duymamış, ya da “bir yerde görmüştüm ama ne işe yarıyor bilmiyorum” kategorisinde bırakmış. Oysa bu özellik, özellikle pipe zincirlerinin yetersiz kaldığı durumlarda hayat kurtarıcı. Bugün bu konuyu hem teorik hem de gerçek dünya senaryolarıyla masaya yatıracağız.
Process Substitution Nedir ve Neden İhtiyaç Duyulur?
Klasik Unix felsefesinde her şey ya bir dosyadır ya da bir dosya gibi davranır. Bu prensibin pratiğe yansımasını en iyi process substitution’da görüyoruz. Temel soruyu sormakla başlayalım: bir komutun çıktısını başka bir komuta dosya olarak vermek istiyorsanız ne yaparsınız?
İlk akla gelen geçici dosya yaratmak:
komut1 > /tmp/gecici.txt
komut2 /tmp/gecici.txt
rm /tmp/gecici.txt
Bu yaklaşım işe yarar ama sorunları var: disk yazma yükü, temizlik sorumluluğu, paralel çalışma imkansızlığı. Process substitution ise bunu şöyle çözer:
komut2 <(komut1)
Bash, <(komut1) ifadesini değerlendirdiğinde arka planda bir süreç başlatır, çıktısını /dev/fd/63 gibi bir dosya tanımlayıcısına bağlar ve bu dosya tanımlayıcısının yolunu komut2‘ye argüman olarak geçer. Geçici dosya yok, disk yazma yok, her şey bellek ve pipe üzerinden akar.
/dev/fd Altyapısı: Perde Arkası
/dev/fd dizini Linux’ta sanal bir dosya sistemidir. Her sürecin açık dosya tanımlayıcılarını, dosyaymış gibi erişilebilir hale getirir. Bunu doğrudan görebilirsiniz:
ls -la /dev/fd/
# veya
ls -la /proc/self/fd/
Process substitution kullandığınızda bash, sizin için bir pipe oluşturur ve bu pipe’ın okuma ucunu /dev/fd/N üzerinden erişilebilir yapar. Yani <(komut) yazdığınızda aslında şunlar olur:
- Bash bir pipe açar
komut‘u fork edip pipe’ın yazma ucuna stdout’unu bağlar- Pipe’ın okuma ucunun file descriptor numarasını alır
- Bu numaraya karşılık gelen
/dev/fd/Nyolunu argüman olarak kullanır
# Bunu kendiniz gözlemleyebilirsiniz:
echo <(echo merhaba)
# Çıktı: /dev/fd/63 (ya da benzer bir sayı)
# Dosyaymış gibi okuyabilirsiniz:
cat <(echo "Bu bir process substitution çıktısı")
Temel Syntax ve Kullanım Biçimleri
Bash’te iki yönde process substitution vardır:
<(komut): Komutun çıktısını bir dosya olarak sunar. Okuma amaçlı kullanılır.
>(komut): Bir hedefe yazmak istediğinizde, yazılanları başka bir komuta yönlendirir. Yazma amaçlı kullanılır.
Basit bir karşılaştırma örneğiyle başlayalım:
# İki dizinin içeriğini diff ile karşılaştır
# Geçici dosyalara gerek yok
diff <(ls /etc/nginx/) <(ls /etc/apache2/)
# Veya iki komutun çıktısını karşılaştır
diff <(cat /etc/hosts) <(ssh uzak-sunucu cat /etc/hosts)
Bu örnekte diff iki dosya argümanı bekliyor. Process substitution sayesinde iki ayrı komutun çıktısını sanki iki ayrı dosyaymış gibi veriyoruz. Geçici dosya oluşturmak zorunda değiliz, pipe zinciri kurmak zorunda değiliz.
Gerçek Dünya Senaryosu 1: Log Analizi
Günlük işlerimden biri: farklı sunuculardaki log dosyalarını karşılaştırmak. Diyelim ki iki uygulama sunucunuz var ve her ikisinde de aynı servis çalışıyor. Hangi sunucuda hangi hata mesajları var, farkı nedir?
# İki sunucudaki nginx error loglarından sadece hata satırlarını çekip karşılaştır
diff
<(ssh web01 "grep 'ERROR' /var/log/nginx/error.log | sort -u")
<(ssh web02 "grep 'ERROR' /var/log/nginx/error.log | sort -u")
Ya da daha pratik bir senaryo: bir log dosyasının belirli bir zaman dilimindeki içeriğini analiz edip, bunu başka bir araçla işlemek:
# Son 1 saatin loglarını filtrele ve wc ile say
wc -l <(awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$0 ~ d' /var/log/nginx/access.log)
Gerçek Dünya Senaryosu 2: Yapılandırma Dosyası Doğrulama
Bir sistemde yapılandırma değişikliği yapmadan önce mevcut ile planlanan yapılandırmayı karşılaştırmak istiyorsunuz. Template dosyanız var, değişkenleri dolduruyorsunuz ve farkı görmek istiyorsunuz:
# Template'den üretilen çıktı ile mevcut dosyayı karşılaştır
diff
/etc/myapp/config.conf
<(envsubst < /etc/myapp/config.conf.template)
Ya da bir playbook çalıştırmadan önce Ansible dry-run çıktısını kaydetmek istemiyorsunuz ama analiz etmek istiyorsunuz:
# Ansible dry-run çıktısını doğrudan grep'e ver
grep "changed" <(ansible-playbook site.yml --check 2>&1)
tee ile Yazma Yönlü Process Substitution
>(komut) biçimi daha az bilinir ama son derece güçlüdür. tee komutuyla kullanımı klasik bir örnektir:
# Bir işlemin çıktısını hem dosyaya yaz hem de aynı anda sıkıştır
some_command | tee >(gzip > output.gz) > output.txt
# Daha da ileri gidebiliriz: çıktıyı üç yere aynı anda gönder
some_command | tee >(gzip > output.gz) >(grep ERROR > errors.txt) > output.txt
Bu özellikle büyük veri akışlarında çok işe yarar. Bir yandan ham çıktıyı saklıyorsunuz, öte yandan paralel olarak hata satırlarını ayırıyorsunuz, öte yandan sıkıştırılmış arşiv oluşturuyorsunuz. Tek geçişte, disk I/O minimumda.
# Veritabanı dump'ını hem sıkıştır hem de doğrudan uzak sunucuya aktar
mysqldump mydb | tee >(gzip > /backup/mydb_$(date +%Y%m%d).sql.gz) | ssh backup-server "cat > /remote/backup/mydb_latest.sql"
Process Substitution ile Named Pipe Arasındaki Fark
Aynı işi named pipe (FIFO) ile de yapabilirsiniz, ama process substitution çok daha temiz:
# Named pipe yöntemi - zahmetli
mkfifo /tmp/mypipe
komut1 > /tmp/mypipe &
komut2 /tmp/mypipe
rm /tmp/mypipe
# Process substitution yöntemi - temiz
komut2 <(komut1)
Named pipe’ların avantajı shell bağımsız olmalarıdır. sh veya dash kullanan ortamlarda process substitution yoktur. Ancak bash veya zsh kullandığınız modern ortamlarda process substitution her zaman tercih edilmelidir.
Gelişmiş Kullanım: Birden Fazla Process Substitution
Bash aynı anda birden fazla process substitution kullanmanıza izin verir. Bu özellik özellikle çok yönlü karşılaştırma ve birleştirme işlemlerinde işe yarar:
# Üç farklı sunucudaki cron job listelerini karşılaştır
diff3
<(ssh sunucu1 crontab -l)
<(ssh sunucu2 crontab -l)
<(ssh sunucu3 crontab -l)
# Birden fazla dosya kaynağını birleştirip işle
sort -u <(cat /etc/group) <(getent group) | grep -v "^#"
Şimdi daha karmaşık bir senaryo: sistemdeki tüm kullanıcıların son giriş zamanlarını iki farklı kaynaktan alıp birleştirmek:
# /etc/passwd'deki kullanıcılarla last komutunu karşılaştır
comm -23
<(awk -F: '$3 >= 1000 {print $1}' /etc/passwd | sort)
<(last -w | awk '{print $1}' | sort -u | grep -v '^$|reboot|wtmp')
/dev/stdin, /dev/stdout, /dev/stderr
Bu sanal dosyalar da benzer şekilde çalışır ve bazı durumlarda process substitution’ın yerini tutabilir:
# /dev/stdin ile pipe içinden dosya bekleyen bir komuta veri gönder
echo "test verisi" | komut /dev/stdin
# Bazı araçlar - (tire) karakterini stdin olarak kabul eder
# ama /dev/stdin her zaman daha güvenilirdir
cat /etc/hosts | grep "local" | komut /dev/stdin
Özellikle bazı eski araçlar stdin’den okuma yaparken dosya yolu bekler. Bu durumda /dev/stdin oldukça kullanışlıdır.
Gerçek Dünya Senaryosu 3: Güvenlik Denetimi
Bir sistemi denetlerken kurulu paketlerin beklenen listesiyle örtüşüp örtüşmediğini kontrol etmek istiyorsunuz:
# Sistemde kurulu paketlerle onaylı paket listesini karşılaştır
diff
<(dpkg --get-selections | grep -v deinstall | awk '{print $1}' | sort)
<(sort /etc/approved-packages.txt)
Ya da SSL sertifikalarını kontrol etmek:
# Bir sunucunun sertifikasını yerel sertifikayla karşılaştır
diff
<(openssl x509 -in /etc/ssl/certs/myapp.crt -text -noout)
<(echo | openssl s_client -connect myapp.example.com:443 2>/dev/null | openssl x509 -text -noout)
Dikkat Edilmesi Gereken Noktalar
Bash bağımlılığı: Process substitution bash ve zsh’a özgüdür. Script’lerinizin başında #!/bin/bash kullandığınızdan emin olun. #!/bin/sh ile process substitution çalışmaz.
Hata kodları: Process substitution içindeki komutun hata kodu ana akışı etkilemez. Bunu özellikle set -e kullandığınızda göz önünde bulundurun:
# Bu durumda iç komutun hata kodu kaybolur
diff <(komut_basarisiz_olabilir) <(diger_komut)
# Daha güvenli yaklaşım için pipefail kullanın
set -o pipefail
Dosya konumu argümanı: Process substitution bir dosya yolu üretir ama bu yol seek edilebilir değildir. Yani lseek() çağrısı yapan araçlar (dosyada ileri geri atlayan araçlar) bu yolla düzgün çalışmayabilir.
Race condition: Çok nadir de olsa, process substitution içindeki süreç henüz başlamadan dış komut /dev/fd/N‘yi açmaya çalışırsa sorun yaşanabilir. Pratik kullanımda bu neredeyse hiç sorun olmaz ama teorik olarak bilinmesi gerekir.
İpucu: Process Substitution’ı Debug Etmek
Nasıl çalıştığını anlamak için şu teknikleri kullanabilirsiniz:
# bash -x ile process substitution'ın ne ürettiğini görün
bash -x -c 'diff <(echo a) <(echo b)'
# Veya doğrudan file descriptor'ı inceleyin
exec 63< <(ls /tmp)
cat /dev/fd/63
exec 63<&-
# Kaç tane file descriptor açıldığını gör
ls /proc/self/fd/ | wc -l
ls /proc/self/fd/ | wc -l # tekrar çalıştırarak karşılaştır
Performans Değerlendirmesi
Büyük veri işlerken process substitution’ın geçici dosyaya göre avantajını ölçmek için basit bir kıyaslama:
# Geçici dosya yöntemi
time (
seq 1000000 | sort > /tmp/test1.txt
seq 500000 1000000 | sort > /tmp/test2.txt
comm -12 /tmp/test1.txt /tmp/test2.txt > /dev/null
rm /tmp/test1.txt /tmp/test2.txt
)
# Process substitution yöntemi
time (
comm -12 <(seq 1000000 | sort) <(seq 500000 1000000 | sort) > /dev/null
)
Disk tabanlı geçici dosyalar her zaman ek I/O yükü getirir. RAM’de yer alan pipe buffer’ları (küçük veri setleri için) veya kernel’ın pipe mekanizması (büyük veri setleri için) genellikle daha hızlı çalışır.
Sonuç
Process substitution, Unix/Linux felsefesinin “her şey dosyadır” prensibini pratiğe döken zarif bir araçtır. Özellikle şu durumlarda başvurulacak ilk seçenek olmalıdır:
- İki veya daha fazla komutun çıktısını karşılaştırmak gerektiğinde
- Geçici dosya oluşturmanın zahmetli veya riskli olduğu durumlarda
teeile çıktıyı paralel olarak birden fazla hedefe yönlendirmek gerektiğinde- SSH üzerinden uzak komutların çıktısını lokal araçlarla işlemek gerektiğinde
/dev/fd altyapısını anlamak ise bu özelliği körü körüne kullanmak yerine sınırlarını ve potansiyelini bilerek kullanmanızı sağlar. Bir file descriptor’ın seek edilemez bir pipe olduğunu, bash’in perde arkasında bir süreç başlattığını ve bu sürecin çıktısını bir dosya tanımlayıcısı üzerinden erişilebilir kıldığını bilmek, sorun yaşadığınızda nereye bakacağınızı anlamanızı kolaylaştırır.
Shell scripting dünyasında “bunu geçici dosyayla yapabilirim ama daha temiz bir yolu var mı?” diye sorduğunuzda, cevap çoğunlukla process substitution’dır. Bu araçla tanıştıktan sonra komut satırı alışkanlıklarınızın nasıl değiştiğini kendiniz göreceksiniz.
