Ders 03 / 24
Boru Hattı
Boru işleci, süreçlerin eşzamanlı çalışması, boru arabelleği ve geri basınç, zincirin çıkış kodu ve alt kabukta kaybolan değişkenler.
İçindekiler
Yönlendirme, bir akışın ucuna dosya koyar. Dosya kalıcı bir aracıdır: yazan komut bitmeden okuyan komut başlayamaz ve ara sonuç diske iner. Oysa çoğu durumda ara sonuç istenmez; istenen, bir komutun ürettiği metnin doğrudan bir sonrakine geçmesidir.
Boru hattı (pipeline) bunu sağlar. Bu ders boru işlecinin ne yaptığını, bu düzenin neden yalnızca bir yazım kısaltması olmadığını ve getirdiği iki tuzağı ele alır.
Boru İşleci
| işleci, solundaki komutun standart çıktısını sağındakinin standart girdisine bağlar.
Aradaki bağlantı bir dosya değil, çekirdeğin sağladığı boru (pipe) adlı bellek içi
tampondur.
Kütükteki istemci adreslerini tekilleştirmek iki komutun bileşimidir:
cut -d' ' -f1 erisim.log | sort -u
10.0.0.12 10.0.0.31 10.0.2.7 172.16.0.9 192.168.1.5
cut, satırları boşluğa göre bölüp birinci alanı yazar; sort -u sıralar ve yinelenen
satırları eler. İkisinin arasında hiçbir dosya yaratılmadı.
Zincir uzatılabilir. Aşağıdaki hat, her adresin kaç istek yaptığını çoktan aza doğru sıralar ve kursun raporlama betiğinin ilk gövdesini oluşturur:
cut -d' ' -f1 erisim.log | sort | uniq -c | sort -rn
7 10.0.0.31 7 10.0.0.12 6 10.0.2.7 5 192.168.1.5 5 172.16.0.9
Aynı hattın yedinci alanla kurulması, istenen yolların dağılımını verir:
cut -d' ' -f7 erisim.log | sort | uniq -c | sort -rn | head -5
6 /api/veri 4 /yok.html 4 /index.html 3 /urun/45 3 /urun/12
Bu dört adımlı desen — çıkar, sırala, say, sırala — kabuk metin işlemenin en sık
kullanılan kalıbıdır. uniq -c yalnızca komşu yinelemeleri saydığı için önündeki
sort isteğe bağlı değildir; onsuz sayım yanlış çıkar.
Süreçler Eşzamanlı Çalışır
Boru hattı, “önce sol biter, sonra sağ başlar” biçiminde çalışmaz. Kabuk hattaki tüm komutları aynı anda başlatır; veri üretildikçe akar.
Bu, gözlemlenebilir bir olgudur. Aşağıdaki hat bir milyon satır üretmeye niyetli bir komutla başlar ama anında sonuçlanır:
seq 1 1000000 | head -3
1 2 3
Sıralı çalışsaydı bir milyon satırın tamamı üretilir, sonra ilk üçü alınırdı. Olan şudur:
head üç satırı okuduktan sonra çıkar ve borunun okuma ucunu kapatır. Kapalı boruya
yazmaya çalışan seq, çekirdekten SIGPIPE sinyali alır ve sonlanır.
Eşzamanlılığın iki pratik sonucu vardır. Birincisi, hattın belleği ara sonucun büyüklüğüyle değil, boru tamponunun sabit boyutuyla sınırlıdır: sınırsız büyüklükte veri sabit bellekte işlenebilir. İkincisi, sonucun ilk satırları, girdinin tamamı okunmadan görünmeye başlar.
İkinci sonucun istisnası vardır: sort gibi komutlar tanım gereği tüm girdiyi görmeden
tek satır bile yazamaz. Böyle bir komut hattın ortasındaysa akış orada durur ve tamamı
biriktikten sonra devam eder. Hattın belleği de bu noktada girdi büyüklüğüne bağlanır.
Geri Basınç
Boru tamponunun sabit boyutlu olması, hızlı bir üreticiyle yavaş bir tüketicinin buluştuğu durumu da çözer. Tampon dolduğunda yazan sürecin yazma çağrısı bloklanır; okuyan süreç tamponu boşalttıkça yazan devam eder. Bu düzenleme geri basınç (backpressure) olarak adlandırılır.
Sonuç: boru hattında hızlı komut, yavaş komutu asla veriye boğamaz. Bellek tüketimi zincirin en yavaş halkasına göre değil, tampon boyutuna göre sabittir.
Zincirin Çıkış Kodu
Bir boru hattının çıkış kodu, öntanımlı olarak yalnızca son komutun kodudur. Aradaki komutların başarısızlığı görünmez olur.
grep 'YOKBOYLE' erisim.log | sort > /dev/null echo "durum=$?"
durum=0
grep eşleşme bulamadığı için başarısızlık bildirdi, ama hattın kodu sort’un kodudur
ve sort boş girdiyi sorunsuz sıraladı. Bir betikte bu, sessiz hataların en verimli
kaynağıdır.
Bash iki çözüm sunar. Birincisi PIPESTATUS dizisidir; hattaki her komutun kodunu sırayla
tutar:
grep -c 'YOKBOYLE' erisim.log | wc -l echo "PIPESTATUS=${PIPESTATUS[*]}"
1 PIPESTATUS=1 0
İkincisi pipefail seçeneğidir. Açıkken hattın kodu, sıfırdan farklı kod döndüren en
sağdaki komutun kodudur:
set -o pipefail grep 'YOKBOYLE' erisim.log | sort > /dev/null echo "pipefail ile durum=$?"
pipefail ile durum=1
PIPESTATUS ve pipefail bash eklentileridir; POSIX kabuk dilinde tanımlı değildir.
Taşınabilirlik gerekiyorsa ara sonucu geçici dosyaya alıp her adımın kodunu ayrı sınamak
gerekir. Sağlam betik yazımı konusunda pipefail, katı kipin sabit parçası olacak.
pipefail’in bir yan etkisi vardır: SIGPIPE ile sonlanan komutlar da başarısız sayılır.
Yukarıdaki seq | head hattı pipefail altında sıfırdan farklı kod döndürür. Erken
kesilen hatlarda bu kodun beklenen olduğunu ayrıca ele almak gerekir.
Alt Kabukta Kaybolan Değişkenler
Boru hattının her bileşeni ayrı bir süreçte çalışır. Kabuğun kendi yerleşik komutları bile hattın içindeyse alt kabuk (subshell) adı verilen bir çocuk süreçte yürütülür. Alt kabuk, ana kabuğun değişkenlerinin bir kopyasıyla başlar; yaptığı değişiklikler kendisiyle birlikte yok olur.
sayac=0 cut -d' ' -f9 erisim.log | while read -r kod; do if [ "$kod" = "404" ]; then sayac=$((sayac + 1)); fi done echo "boru hatti sonrasi sayac=$sayac"
boru hatti sonrasi sayac=0
Döngü dört kez saydı, ama sayım alt kabukta kaldı. Bu, kabuk betiklerinde en çok zaman kaybettiren davranışlardan biridir; hata mesajı üretmez, yalnızca yanlış sonuç verir.
Çözüm, döngüyü hattın sağ ucundan çıkarmaktır. Bash’te süreç yerine koyma (process substitution) bir komutun çıktısını dosya gibi gösterir ve döngü ana kabukta kalır:
sayac=0 while read -r kod; do if [ "$kod" = "404" ]; then sayac=$((sayac + 1)); fi done < <(cut -d' ' -f9 erisim.log) echo "yeniden yonlendirme ile sayac=$sayac"
yeniden yonlendirme ile sayac=4
< <(...) yazımındaki iki karakter dizisi ayrıdır: sağdaki <(...) süreç yerine
koymadır, soldaki < girdi yönlendirmesidir; aralarındaki boşluk zorunludur. Süreç
yerine koyma da bash eklentisidir. POSIX kabuk dilinde aynı sonuç, sayımı alt kabuktan
metin olarak geri okuyarak elde edilir; bu yol Aritmetik ve Komut Yerine Koyma dersinde
gösterilecek.
Programlama Temelleri kursunda kapsam, adların hangi bölgede görünür olduğu ekseninde tanımlanmıştı. Kabukta ek bir eksen vardır: adın hangi süreçte yaşadığı. İki süreç arasında değişken geçişi yoktur; yalnızca metin geçişi vardır.
Hata Akışını Zincire Sokmak
| yalnızca standart çıktıyı bağlar. Tanı iletilerinin de bir sonraki komuta gitmesi
isteniyorsa, çoğaltma boru işlecinden önce yazılır:
ls erisim.log yok.txt 2>&1 | sort
erisim.log ls: yok.txt: No such file or directory
Bash bunun kısaltması olarak |& yazımını tanır; POSIX kabuk dilinde bulunmaz.
Bu birleştirme temkinli kullanılmalıdır: veri ile tanı iletisini aynı akışta topladığınız anda, bir sonraki komut hata metnini de veri sanar. Önceki dersteki kural burada da geçerlidir — ayrım bilinçli olarak bozulmalıdır, kolaylık olsun diye değil.
Özet
- Boru işleci, sol komutun standart çıktısını sağ komutun standart girdisine bağlar; arada dosya değil, sabit boyutlu bir çekirdek tamponu vardır.
- Hattaki komutlar eşzamanlı çalışır; okuma ucu kapandığında yazan süreç
SIGPIPEile sonlanır ve gereksiz üretim durur. - Tampon dolduğunda yazan bloklanır; geri basınç sayesinde bellek tüketimi zincir boyunca sabit kalır.
- Hattın öntanımlı çıkış kodu son komutunkidir; ara başarısızlıklar
PIPESTATUSveyapipefailile görünür kılınır. - Hattın her bileşeni alt kabukta çalıştığı için sağ uçtaki atamalar ana kabuğa geri dönmez; döngüyü hattan çıkarmak gerekir.
Sonraki Adım
Şimdiye kadarki girdiler ya klavyeden ya da var olan bir dosyadan geldi. Betikler
çoğu zaman üçüncü bir kaynağa ihtiyaç duyar: kendi içine gömülü sabit metin —
yapılandırma şablonu, çok satırlı ileti, sınama verisi. Sonraki ders burada belge ve
burada dizgi gösterimlerini ele alır ve bu kursun erisim.log dosyasını tek komutla
yeniden üretmenin yolunu gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.