Ders 13 / 24
Aritmetik ve Komut Yerine Koyma
Tam sayı aritmetiği ve tuzakları, aritmetik komutun ters çıkış kodu, ondalıklı hesabın dışarıya devri, komut çıktısının değere çevrilmesi.
İçindekiler
Önceki iki derste $(( ... )) ve $( ... ) gösterimleri gerekçelendirilmeden kullanıldı.
İkisi de kabuğun temel araçlarıdır ve ikisinin de sessizce yanlış sonuç üreten köşeleri
vardır.
Değişkenler dersinde kurulan ilke geçerliliğini koruyor: kabukta her değer bir karakter dizisidir. Bu iki gösterim, o dizileri geçici olarak başka bir alana taşıyıp geri getirir — biri sayı alanına, diğeri komut çıktısı alanına.
Aritmetik Genişletme
$(( ifade )) gösterimi, içindeki ifadeyi tam sayı olarak değerlendirir ve sonucu metin
olarak yerine koyar.
echo "$(( 7 / 2 )) $(( -7 / 2 )) $(( 7 % 2 )) $(( 2 ** 10 ))"
3 -3 1 1024
Bölme sıfıra doğru kırpar: için 3, için verir. Aşağı yuvarlama yapan dillerden farklıdır; negatif sayılarla çalışırken sonuç ayrımı önemlidir.
İfade içinde değişkenler dolar işareti olmadan yazılabilir; ayraç içi zaten sayı alanıdır:
a=6; b=4 echo "$(( a + b )) $(( $a * $b ))"
10 24
Ayraç içinde boşluk serbesttir ve kelime bölme uygulanmaz. $(( 1+2 * 3 )) ifadesi 7
verir; öncelik kuralları Programlama Temelleri kursundaki işleç önceliğiyle aynıdır.
Taban Tuzağı
Kabuk, sıfırla başlayan sayıyı sekizlik (octal) kabul eder. Sıfır dolgulu alanlardan gelen değerler — saat, dakika, sıra numarası — bu yüzden hata üretir:
echo "$(( 08 ))"
bash: 08: value too great for base (error token is "08")
08 sekizlik tabanda geçerli değildir. Tabanı açıkça bildirmek gerekir:
echo "$(( 10#08 ))"
8
10# öneki değeri onluk tabanda okur. Dış kaynaktan gelen ve sıfır dolgulu olabilecek
her değerde kullanılmalıdır.
Aritmetik Komut ve Ters Çıkış Kodu
(( ifade )) bir genişletme değil, bir komuttur: değeri yerine koymaz, yalnızca hesaplar
ve bir çıkış kodu üretir. Kod, değerin tersidir: sonuç sıfırdan farklıysa 0 (başarı),
sonuç sıfırsa 1 (başarısızlık).
(( 1 + 1 )); echo "sonuc 2 -> kod=$?" (( 0 )); echo "sonuc 0 -> kod=$?"
sonuc 2 -> kod=0 sonuc 0 -> kod=1
Bu tersleme rastgele değil, iki sözleşmenin uzlaştırılmasıdır: C benzeri dillerde sıfırdan farklı değer “doğru”dur, kabukta ise 0 kodu “başarı”dır. Sonuç, aritmetik koşulları doğal biçimde yazılabilir kılar:
(( 5 > 3 )) && echo "karsilastirma dogru"
karsilastirma dogru
Tuzak, katı kip altında ortaya çıkar. (( sayac++ )) ifadesi sayac sıfırken 1 kodu
döndürür; set -e etkin bir betikte bu, betiği durdurur. Sayaç artırmanın güvenli
yazımı sayac=$(( sayac + 1 )) biçimidir. Bu tuzak Hata Yönetimi dersinde yeniden ele
alınacak.
(( )) içinde ++, --, += gibi işleçler kullanılabilir:
sayac=0; (( sayac++ )); (( sayac += 5 )); echo "sayac=$sayac"
sayac=6
Eski betiklerde aynı iş için let ve expr görülür. let bash eklentisidir ve
(( )) ile eşdeğerdir. expr ayrı bir programdır; her çağrıda bir süreç yaratır ve
işleçlerinin kabuk tarafından yorumlanmaması için kaçırılması gerekir. Yeni betiklerde
ikisi de gerekli değildir.
Ondalıklı Hesap Kabukta Yoktur
Kabuk aritmetiği yalnızca tam sayıdır. Ondalıklı sonuç gerektiğinde iş dışarıya devredilir.
awk 'BEGIN { printf "%.2f\n", 4 / 30 * 100 }'
13.33
Rasgele duyarlıkta hesap yapan bir hesap makinesi aracı da kullanılabilir, ancak
scale ayarının her bölme işleminde uygulandığı unutulmamalıdır:
echo 'scale=2; 4 / 30 * 100' | bc echo 'scale=2; 4 * 100 / 30' | bc
13.00 13.33
İlk yazımda bölme önce yapıldı ve sonuç 0.13’e kırpıldı; çarpma bu kırpılmış değerle sürdü. İşlem sırasını çarpma bölmeden önce gelecek biçimde kurmak, sabit duyarlıklı hesapta genel bir kuraldır.
Üçüncü yol, ölçeklendirilmiş tam sayı aritmetiğidir. Ondalık noktayı elle yerleştirmek, dış araç çağırmadan bir hane duyarlık verir:
pay=4; toplam=30 printf '%d.%d\n' $(( pay * 1000 / toplam / 10 )) $(( pay * 1000 / toplam % 10 ))
13.3
Bu yöntem yuvarlama değil kırpma yapar. Rapor niteliğindeki çıktılarda yeterlidir; parasal ya da bilimsel hesapta değildir.
Komut Yerine Koyma
$( komut ) gösterimi, komutu çalıştırır, standart çıktısını yakalar ve o metni yerine
koyar.
adet=$(wc -l < erisim.log) echo "[$adet]"
[ 30]
Baştaki boşluklar geldi — bu, birinci derste değinilen hizalama farkıdır. Aritmetik genişletme boşlukları soğurur:
adet=$(( $(wc -l < erisim.log) )) echo "[$adet]"
[30]
Bu iç içe yazım, komut çıktısını sayıya çevirmenin standart yoludur.
Yerine Koymanın Uyguladığı Dönüşümler
Üç davranış bilinmelidir.
Sondaki satır sonları silinir. Komutun çıktısındaki tüm sondaki satır sonları atılır; aradakiler korunur.
d=$(printf 'a\n\n\n') printf '<%s>\n' "$d"
<a>
Bu davranış çoğu zaman istenendir, ama satır sonu anlamlı olan verilerde kayba yol açar.
Tırnaksız yazım bölünür. Değişken genişletmesinde geçerli olan kural burada da geçerlidir:
say() { printf 'sayi=%d\n' "$#"; }
say $(head -2 erisim.log)
say "$(head -2 erisim.log)"
sayi=20 sayi=1
Tırnaksız yazımda iki satır yirmi kelimeye bölündü. Komut yerine koyma her zaman tırnaklanır.
Çıkış kodu atamayla kaybolmaz ama gizlenebilir. Yalnızca yerine koyma yapan bir atama,
komutun kodunu $? içinde bırakır:
d=$(grep '999' erisim.log); echo "kod=$?"
kod=1
Ancak atamanın önünde local, export ya da declare varsa kod o komuta ait olur ve
gerçek sonuç görünmez; bu tuzak Fonksiyonlar ve Kapsam dersinde gösterilmişti.
Yazım Biçimleri
$( ... ) yazımının eski karşılığı ters tırnaktır. İki nedenle yeğlenmez: iç içe
kullanımda her düzey için ters bölü gerekir ve ters bölünün ters tırnak içindeki anlamı
farklıdır. $( ... ) iç içe yazımda hiçbir kaçış istemez:
echo "en cok istenen: $(cut -d' ' -f7 "$(echo erisim.log)" | sort | uniq -c | sort -rn | head -1)"
en cok istenen: 6 /api/veri
Bir dosyanın tamamını okumak için bash $(< dosya) kısayolunu sunar; süreç yaratmadığı
için $(cat dosya) yazımından ucuzdur:
echo "[$(< tek.txt)]"
[merhaba]
Maliyet
Komut yerine koyma bir alt kabuk ve genellikle bir dış süreç yaratır. Otuz satırlık bir kütükte bu görünmez; döngü içinde satır başına bir çağrı yapan bir betikte, iş yükünün tamamını süreç yaratma oluşturabilir.
Kural: döngü gövdesinde komut yerine koyma varsa, aynı işi tek bir dış araç çağrısıyla yapmanın yolu aranır. Bir sonraki konunun tamamı bu ilkenin uygulanmasıdır.
Betiğe Uygulama
rapor.sh, bir özet bloğu ve yüzde sütunu kazanır:
kutuk=erisim.log
yuzde() { # yuzde PAY TOPLAM -> "13.3"
local pay="$1" toplam="$2"
(( toplam == 0 )) && { echo "0.0"; return; }
printf '%d.%d' $(( pay * 1000 / toplam / 10 )) $(( pay * 1000 / toplam % 10 ))
}
istek=$(( $(wc -l < "$kutuk") ))
bayt=$(awk '{ t += $10 } END { print t + 0 }' "$kutuk")
bolum "Kutuk: $kutuk"
printf ' istek sayisi : %d\n' "$istek"
printf ' toplam bayt : %d\n' "$bayt"
printf ' istek basina : %d bayt\n' $(( istek > 0 ? bayt / istek : 0 ))
./rapor.sh -n 3
== Kutuk: erisim.log == istek sayisi : 30 toplam bayt : 117780 istek basina : 3926 bayt == En cok istenen 3 yol == 6 /api/veri 4 /yok.html 4 /index.html == Durum kodu siniflari == 2xx basarili 18 %60.0 3xx yonlendirme 3 %10.0 4xx istemci hatasi 6 %20.0 5xx sunucu hatasi 3 %10.0
$(( istek > 0 ? bayt / istek : 0 )) yazımındaki üçlü işleç, sıfıra bölmeyi önler. Kabuk
aritmetiğinde sıfıra bölme, betiği durduran bir hatadır; bölen her zaman sınanmalıdır.
Yüzdelerin toplamı 100,0 çıkıyor ve sayıların toplamı istek sayısına eşit. Bu iç tutarlılık, raporun doğruluğunun en ucuz sınamasıdır.
Özet
$(( ))tam sayı aritmetiği yapar, sıfıra doğru kırpar ve içinde değişkenler dolar işaretsiz yazılabilir; sıfırla başlayan sayılar sekizlik sayılır,10#öneki gerekir.(( ))bir komuttur ve çıkış kodu değerin tersidir; sıfır sonuç başarısızlık bildirir.- Ondalıklı hesap kabukta yoktur; dış araca devredilir ya da ölçeklendirilmiş tam sayıyla yapılır.
- Komut yerine koyma sondaki satır sonlarını siler ve tırnaksız yazımda bölünür; her zaman tırnaklanır.
- Her yerine koyma bir alt kabuk maliyetidir; döngü gövdesindeki çağrılar toplu bir dış araç çağrısıyla değiştirilmelidir.
Sonraki Adım
Bu konu betiğin iskeletini tamamladı: yorumlayıcı bildirimi, argüman arayüzü, girdi
sınamaları, fonksiyonlar, döngüler ve hesap. Betik çalışıyor, ama yaptığı işin çoğunu
kaba araçlarla yapıyor: yolları cut ile ayırıyor, durum kodlarını grep -c ile sayıyor
ve her sayım için kütüğü baştan okuyor.
Sonraki konu bu işleri asıl araçlarına devreder. Düzenli ifadelerle başlar — kütükteki istek satırını alanlarına ayırmanın, geçersiz satırları elemenin ve zaman damgasını yeniden biçimlendirmenin ortak dili budur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.