İçeriğe geç
academia.sh

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: 7/27/2 için 3, 7/2-7/2 için 3-3 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat