00 "Do one thing well"
Unix felsefesinin özü: Her program tek bir şeyi iyi yapsın ve diğerleriyle konuşabilsin.
Doug McIlroy'un 1978'de yazdığı kurallar bugün hâlâ geçerli:
"Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface."
Çev.: "Tek bir şeyi iyi yapan programlar yaz. Birlikte çalışan programlar yaz. Metin akışlarını işleyen programlar yaz — çünkü bu evrensel bir arayüzdür."
Pipe'lar bu felsefenin beton dökümü. Tek başına sınırlı olan araçlar, pipeline'da birbirini tamamlar:
# En sık erişilen 10 URL (nginx access.log'dan)
awk '{print $7}' /var/log/nginx/access.log \
| sed 's/?.*//' \
| sort \
| uniq -c \
| sort -rn \
| head -10
Altı araç, her biri tek şey yapıyor. Birlikte bir rapor üretiyorlar.
01 stdin / stdout / stderr
Her sürecin üç standart akışı vardır. Birleştirmenin temeli burası.
File descriptor:
0 → stdin (standart giriş) — klavye veya pipe'tan
1 → stdout (standart çıkış) — terminale veya pipe'a
2 → stderr (standart hata) — hata mesajları
Süreç:
┌─────────────────────┐
│ │
stdin → [ komut ] → stdout
│
stderr
Yönlendirme operatörleri
# stdout → dosya (üzerine yaz)
ls > liste.txt
# stdout → dosya (ekle)
echo "yeni satır" >> liste.txt
# stdin ← dosya
sort < veri.txt
# stderr → dosya
find / -name '*.conf' 2> hatalar.txt
# stdout ve stderr → aynı dosya
komut > cikti.txt 2>&1
# veya (bash kısaltması):
komut &> cikti.txt
# stderr → /dev/null (hataları bastır)
find / -name '*.log' 2>/dev/null
# stdout → /dev/null (çıktıyı bastır, sadece exit code iste)
grep -q 'pattern' dosya.txt > /dev/null
&> dosya kısaltması.komut > dosya 2>&1 ile komut 2>&1 > dosya farklıdır. İlki her ikisini de dosyaya yönlendirir. İkincisi stderr'i önce stdout'a (terminale) bağlar, sonra stdout'u dosyaya yönlendirir — stderr terminale gelir. Her zaman > dosya 2>&1 sırası kullan.
02 Pipe (|) — akışı devret
Bir komutun stdout'unu bir sonraki komutun stdin'ine bağlar. İkisi eş zamanlı çalışır.
# Temel pipe
ls -la | grep '^d' # sadece dizinleri listele
# Zincirleme
cat /var/log/syslog | grep 'error' | tail -20
# Exit code: son komutun exit code'u pipeline'ın exit code'u
grep 'pattern' dosya.txt | wc -l
echo $? # wc'nin exit code'u (genelde 0)
# pipefail: pipeline'da herhangi bir hata olunca başarısız say
set -o pipefail
grep 'yok' /dosya/yok.txt | wc -l
echo $? # → 2 (grep'in exit code, dosya bulunamadı)
# PIPESTATUS: her komutun ayrı exit code'u
grep 'pattern' dosya.txt | sort | wc -l
echo ${PIPESTATUS[@]} # → 0 0 0 (grep sort wc sırasıyla)
Pipe'ta her iki komut eş zamanlı çalışır. sol_komut stdout'a yazar, sağ_komut aynı anda stdin'den okur. Aralarında kernel buffer var (4K-64K). Tam okuma/yazma döngüsü gerekmez — büyük akışlarda bellek dostu.
03 tee — akışı çatalla
tee (T-parçası) akışı ikiye böler: hem ileriye hem bir yere yazar.
# Terminale yaz ve dosyaya kaydet
komut | tee cikti.txt
# Birden fazla dosyaya
komut | tee dosya1.txt dosya2.txt
# Ekle (-a append)
komut | tee -a log.txt
# Pipeline ortasında logla ve devam et
find . -name '*.log' \
| tee /tmp/bulunan_loglar.txt \
| xargs wc -l
# sudo ile dosyaya yaz (redirect'te sudo çalışmaz)
echo 'yeni satır' | sudo tee -a /etc/hosts
# Sadece dosyaya yaz (terminale değil)
# tee /dev/null yerine -a /dev/null ile hata önleme
komut | tee kayit.txt > /dev/null
sudo echo 'x' >> /etc/hosts çalışmaz — yönlendirme shell tarafından yapılır, sudo kapsamı dışında. Doğrusu: echo 'x' | sudo tee -a /etc/hosts — tee root olarak çalışır ve dosyaya yazar.
04 xargs — stdin'den argüman üret
Bir komutun çıktısını bir sonrakinin argümanı yap. Pipe'ın stdin'i desteklemeyen komutlarla çalışmasını sağlar.
# Temel: her satırı argüman yap
echo "dosya1 dosya2 dosya3" | xargs rm
# → rm dosya1 dosya2 dosya3
# find ile klasik kombinasyon
find . -name '*.tmp' | xargs rm
# Kaçta kaç argüman: -n N (her çalıştırmada N argüman)
echo "a b c d" | xargs -n2 echo
# → echo a b
# → echo c d
# Argümanın yeri belirle (-I{})
ls *.jpg | xargs -I{} convert {} {}.webp
# {} her dosya adı için yer tutar
# NUL ayırıcı (-0, find -print0 ile güvenli)
find . -name '*.log' -print0 | xargs -0 gzip
# Paralel çalıştır (-P N: N süreç)
find . -name '*.jpg' -print0 | xargs -0 -P4 -I{} \
convert {} -resize 800x600 out/{}
# Komut satırını göster (-t trace)
echo "a b c" | xargs -t echo
# Boş girişte çalışma (-r / --no-run-if-empty)
find . -name '*.tmp' | xargs -r rm
-I{} ile {} her argümanla değiştirilir.05 Process substitution — <(cmd) ve >(cmd)
Bir komutun çıktısını dosya gibi kullan. Geçici dosyaya yazmadan iki komutun çıktısını karşılaştır.
# Komutun çıktısını dosya gibi sun: <(cmd)
# diff iki dosya bekler; iki komutun çıktısını karşılaştır
diff <(ls /dir1) <(ls /dir2)
# sorted listeleri karşılaştır
diff <(sort a.txt) <(sort b.txt)
# comm: iki sıralı listenin kesişim/farkı
comm <(sort liste1.txt) <(sort liste2.txt)
# Uzak ve yerel dosyayı karşılaştır (geçici dosya gereksiz)
diff <(ssh prod cat /etc/nginx/nginx.conf) nginx.conf.local
# >(cmd): çıktı giden "dosya" olarak sun
tee >(gzip > log.gz) >(grep ERROR > errors.txt) < access.log
# access.log'u hem sıkıştır hem hataları ayrı çek
<(ls /dir1) — bash bir FIFO veya /dev/fd/N oluşturur, ls /dir1 komutunun çıktısını oraya yazar. diff bu dosyayı okur. Geçici dosya oluşturmadan iki komutun çıktısı karşılaştırılmış olur. Bash ve zsh'ta çalışır, POSIX sh'da çalışmaz.
06 Command substitution — $(cmd)
Bir komutun çıktısını değişkene ata veya başka komutun argümanı yap.
# Temel: $(cmd)
TARIH=$(date +%Y-%m-%d)
echo "Bugün: $TARIH"
# Komut argümanı olarak
ls $(cat dizin_listesi.txt)
# Dizine git (en fazla değişen dosyanın dizini)
cd $(git diff --name-only | head -1 | xargs dirname)
# İç içe
echo "Kernel: $(uname -r), python: $(python3 -V 2>&1 | awk '{print $2}')"
# Eski backtick sözdizimi (önerilmez)
TARIH=`date +%Y-%m-%d` # iç içe yazılması zor, kaçış karakteri sorunlu
`outer \`inner\``). Kullanma.Command substitution tırnak içinde kullanılmazsa sonuç kelime bölünmesine uğrar. file=$(ls "Benim Dosyam.txt") değil ls $file — dosya adındaki boşluk iki argümana bölünür. "$file" şeklinde tırnak içinde kullan.
07 Named pipe (FIFO)
Disk üzerinde kalıcı bir pipe noktası. İki farklı terminal veya süreç arasında akış sağlar.
# FIFO oluştur
mkfifo /tmp/benim_pipe
# Terminal 1: yazar (bloke olur, okuyucu bağlanana kadar bekler)
ls -la > /tmp/benim_pipe
# Terminal 2: okuyucu
cat /tmp/benim_pipe
# Temizle
rm /tmp/benim_pipe
# Gerçek kullanım: iki program arasında veri akışı
mkfifo /tmp/video_pipe
# Terminal 1: veri üret (ffmpeg decode)
ffmpeg -i video.mp4 -f rawvideo -pix_fmt rgb24 /tmp/video_pipe
# Terminal 2: veri tüket (python ile işle)
python3 analiz.py < /tmp/video_pipe
Normal pipe tek bir shell içinde çalışır. FIFO (mkfifo) bağımsız iki süreç veya iki terminal arasında kalıcı bir "randevu noktası" sağlar. Biri yazar, diğeri okur — senkronizasyon kernel tarafından yapılır. Producer-consumer senaryoları için kullanışlıdır.
08 Pratik vakalar
Bölümlerdeki araçları gerçek senaryolarda birleştir.
Paralel curl — 10 URL'yi aynı anda indir
# urls.txt: her satırda bir URL
cat urls.txt | xargs -P10 -I{} curl -sO {}
# -P10: 10 paralel curl, -s: sessiz, -O: URL'den dosya adı
İki log'u gerçek zamanlı karşılaştır
diff <(ssh prod1 tail -f /var/log/app.log) \
<(ssh prod2 tail -f /var/log/app.log)
Stream'i logla ama pipeline'da devam et
kubectl logs -f mypod \
| tee pod_$(date +%Y%m%d).log \
| grep --line-buffered 'ERROR' \
| while IFS= read -r line; do
echo "$line" | mail -s "Pod Hatası" admin@example.com
done
Büyük dosyayı paralel sıkıştır
find /var/log -name '*.log' -mtime +7 -print0 \
| xargs -0 -P4 zstd -T1 --rm
Değişken listeden API çağrısı
# user_ids.txt: her satırda bir ID
cat user_ids.txt | xargs -I{} -P5 \
curl -s "https://api.example.com/users/{}" \
| jq -r '.name'
Log'daki en sık IP'yi bul ve engelle
# En sık 429 alan IP → iptables'a ekle
awk '$9 == "429" {print $1}' access.log \
| sort | uniq -c | sort -rn | head -5 \
| awk '{print $2}' \
| xargs -I{} sudo iptables -A INPUT -s {} -j DROP
Hatırlanacaklar
- stdin=0, stdout=1, stderr=2.
2>/dev/null,2>&1 - Pipe: eş zamanlı çalışır, son komutun exit code'u pipeline'ın exit code'u
set -o pipefail: pipeline'da herhangi bir hata sonucu başarısız saytee dosya: hem stdout'a hem dosyaya,tee -aekle,sudo teeroot dosyaları içinxargs -I{}: argümanın yeri,-P N: paralel,-0: NUL ayırıcı<(cmd): komut çıktısını dosya gibi sun (diff, comm için)$(cmd): komut çıktısını değişkene veya argümana çevirmkfifo: iki bağımsız süreç arasında kalıcı pipe noktası- find -print0 | xargs -0: boşluklu dosya adı güvenliği
Pipeline'larda metin işleme için: grep, sed, awk rehberi.
find ile xargs kombinasyonları için: find rehberi.