---
title: 'Kabuk Programlama'
source: 'https://academia.sh/tr/kurslar/kabuk-programlama'
language: tr
updated: '2026-08-17T04:41:53+00:00'
license: 'CC BY-SA 4.0'
lessons: 24
outcomes:
    - 'Girdi/çıktı yönlendirme ve boru hattı kurarak komutları birleştirir'
    - 'Değişken, koşul, döngü ve fonksiyon içeren betikler yazar'
    - 'Düzenli ifadeler ve metin işleme araçlarıyla veri dönüştürür'
---

# Kabuk Programlama

Akışlar ve yönlendirme, betik yapısı, düzenli ifadelerle metin işleme ve sağlam betik yazımı; komutları birleştirerek yeniden çalıştırılabilir araçlar kurma.

## Akışlar ve Boru Hatları

Üç standart akış, yönlendirme işleçleri, boru hattıyla komut zincirleme, betiğe gömülü girdi ve çıkış kodu sözleşmesi.

### Standart Girdi, Çıktı ve Hata

Source: https://academia.sh/tr/kurslar/kabuk-programlama/standart-girdi-cikti-ve-hata

Üç standart akış, dosya tanıtıcı numaraları, veriyle tanı iletisinin ayrılması ve süzgeç modeli.

Linux'a Giriş kursunda komutlar tek tek çalıştırıldı: dizin listelendi, dosya kopyalandı,
izin değiştirildi. Her komutun bir çıktısı oldu ve o çıktı ekrana yazıldı. Ekran, işin
bittiği yerdi.

Bu kurs o varsayımı kaldırır. Kabuğun asıl gücü, bir komutun ürettiği veriyi başka bir
komuta yem yapabilmesinden gelir; bunun için de çıktının nereye gittiğinin komut
tarafından değil, komutu çağıran tarafından belirlenmesi gerekir. İlk soru şudur: bir
komut yazdığı veriyi tam olarak nereye yazar?

#### Üç Akış

Bir süreç (process) başlatıldığında, kabuk ona üç adet açık **akış (stream)** devreder.
Her akış bir tam sayıyla, **dosya tanıtıcısı (file descriptor)** ile anılır:

| Numara | Ad | Yön | Varsayılan bağlantı |
|---|---|---|---|
| 0 | standart girdi (stdin) | okuma | klavye |
| 1 | standart çıktı (stdout) | yazma | ekran |
| 2 | standart hata (stderr) | yazma | ekran |

Bu numaralar sözleşmedir, keşif değil: her program çalışmaya başladığında 0, 1 ve 2
numaralı tanıtıcıların açık olduğunu varsayar. Program bu tanıtıcıların ucunda ne
olduğunu bilmez ve bilmemelidir. Uç, bir terminal olabilir; bir dosya, bir boru veya
bir ağ bağlantısı da olabilir.

Bu bilgisizlik bir eksiklik değil, tasarımın kendisidir. Komut "ekrana yaz" demez,
"1 numaralı tanıtıcıya yaz" der. Nereye gideceğine çağıran karar verir.

#### Kursun Çalışma Dosyası

Bu derste ve sonraki derslerde komutlar tek bir veri kümesi üzerinde çalışacak: bir web
sunucusunun erişim **kütüğü (log)**. Kütük dışarıdan indirilmez; aşağıdaki betik onu
`erisim.log` adıyla üretir, böylece her okur aynı otuz satırla çalışır ve sayılar
birebir tutar. Betikteki `<<'SON'` gösterimi burada belge (here document) adını taşır ve
dördüncü derste ayrıntısıyla anlatılır; şimdilik "şu satırları dosyaya yaz" diye okuyun.

```bash
#!/usr/bin/env bash
# veri-uret.sh
cat > erisim.log <<'SON'
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /statik/stil.css HTTP/1.1" 200 2048
10.0.0.12 - - [07/Feb/2024:09:13:02 +0000] "GET /urun/12 HTTP/1.1" 200 8410
172.16.0.9 - - [07/Feb/2024:09:14:19 +0000] "GET /yok.html HTTP/1.1" 404 512
10.0.2.7 - - [07/Feb/2024:09:15:30 +0000] "POST /giris HTTP/1.1" 302 0
10.0.2.7 - - [07/Feb/2024:09:15:31 +0000] "GET /panel HTTP/1.1" 200 12040
10.0.0.31 - - [07/Feb/2024:09:16:04 +0000] "GET /urun/45 HTTP/1.1" 200 7330
192.168.1.5 - - [07/Feb/2024:09:17:22 +0000] "GET /api/veri HTTP/1.1" 500 180
10.0.0.12 - - [07/Feb/2024:09:18:00 +0000] "GET /api/veri HTTP/1.1" 200 3300
172.16.0.9 - - [07/Feb/2024:09:18:47 +0000] "GET /gizli HTTP/1.1" 403 290
10.0.0.31 - - [07/Feb/2024:09:19:12 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.2.7 - - [07/Feb/2024:09:20:05 +0000] "GET /urun/12 HTTP/1.1" 200 8410
192.168.1.5 - - [07/Feb/2024:09:21:38 +0000] "GET /api/veri HTTP/1.1" 500 180
10.0.0.12 - - [07/Feb/2024:09:22:10 +0000] "GET /statik/stil.css HTTP/1.1" 304 0
172.16.0.9 - - [07/Feb/2024:09:23:55 +0000] "GET /yok.html HTTP/1.1" 404 512
10.0.0.31 - - [07/Feb/2024:09:24:41 +0000] "POST /api/veri HTTP/1.1" 201 96
10.0.2.7 - - [07/Feb/2024:09:25:19 +0000] "GET /index.html HTTP/1.1" 200 5120
192.168.1.5 - - [07/Feb/2024:09:26:33 +0000] "GET /urun/45 HTTP/1.1" 200 7330
10.0.0.12 - - [07/Feb/2024:09:27:02 +0000] "GET /panel HTTP/1.1" 200 12040
172.16.0.9 - - [07/Feb/2024:09:28:14 +0000] "GET /eski-sayfa HTTP/1.1" 301 0
10.0.0.31 - - [07/Feb/2024:09:29:48 +0000] "GET /api/veri HTTP/1.1" 200 3300
10.0.2.7 - - [07/Feb/2024:09:30:26 +0000] "GET /statik/stil.css HTTP/1.1" 200 2048
192.168.1.5 - - [07/Feb/2024:09:31:07 +0000] "GET /yok.html HTTP/1.1" 404 512
10.0.0.12 - - [07/Feb/2024:09:32:53 +0000] "GET /urun/12 HTTP/1.1" 200 8410
10.0.0.31 - - [07/Feb/2024:09:33:39 +0000] "GET /gizli HTTP/1.1" 403 290
172.16.0.9 - - [07/Feb/2024:09:34:11 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.2.7 - - [07/Feb/2024:09:35:44 +0000] "POST /api/veri HTTP/1.1" 500 180
192.168.1.5 - - [07/Feb/2024:09:36:58 +0000] "GET /panel HTTP/1.1" 200 12040
10.0.0.12 - - [07/Feb/2024:09:37:20 +0000] "GET /urun/45 HTTP/1.1" 200 7330
10.0.0.31 - - [07/Feb/2024:09:38:05 +0000] "GET /yok.html HTTP/1.1" 404 512
SON
```

Betiği `veri-uret.sh` adıyla kaydedin ve bir kez `bash veri-uret.sh` komutuyla
çalıştırıp çalışma dizininizde otuz satırlık `erisim.log` dosyasını oluşturun.

Her satırın alanları boşlukla ayrılmıştır: istemci adresi, iki kullanılmayan alan, köşeli
ayraç içinde zaman damgası, tırnak içinde istek satırı (yöntem, yol, protokol), yanıt
durum kodu ve gönderilen bayt sayısı. Kursun sonunda bu dosyayı okuyup özet rapor üreten,
hataya dayanıklı ve zamanlanmış olarak çalışan bir betik yazılmış olacak.

#### Neden İki Ayrı Çıkış Akışı

Yazma yönünde iki akış olması ilk bakışta gereksiz görünür. Gerekçesi şudur: bir komutun
ürettiği iki tür metin vardır ve bunlar farklı yerlere gitmelidir.

- **Veri**, komutun asıl ürünüdür: dosya listesi, satır sayısı, süzülmüş metin. Başka
  bir komuda girdi olabilecek olan budur. Standart çıktıya gider.
- **Tanı iletisi (diagnostic message)**, insan içindir: hata bildirimi, uyarı, ilerleme
  bilgisi. Bir sonraki komuda girdi olarak kullanılmamalıdır. Standart hataya gider.

Ayrım pratikte hemen görülür. Aşağıdaki komut var olan bir dosya ile var olmayan bir adı
birlikte listeler:

```sh
ls erisim.log yok.txt
```

```
ls: cannot access 'yok.txt': No such file or directory
erisim.log
```

İki satır aynı ekranda göründü, ama aynı akıştan gelmediler. Bunu doğrulamak için
akışlardan birini ıskartaya çıkarmak yeterlidir. `/dev/null`, kendisine yazılan her şeyi
yutan özel bir aygıt dosyasıdır:

```sh
ls erisim.log yok.txt 2>/dev/null
```

```
erisim.log
```

```sh
ls erisim.log yok.txt 1>/dev/null
```

```
ls: cannot access 'yok.txt': No such file or directory
```

`2>` ve `1>` gösterimlerinin ayrıntısı sonraki dersin konusudur; burada önemli olan
sonuçtur. İlk çağrıda yalnızca veri, ikincisinde yalnızca tanı iletisi kaldı. Demek ki
`ls`, bulamadığı dosya için ürettiği iletiyi bilerek ikinci akışa yazmıştır.

Bu davranış bir gelenek değil, uyulması beklenen bir kuraldır. Kendi betiklerinizde
uyarıları standart çıktıya yazarsanız, betiğinizin çıktısını işleyen bir sonraki komut
uyarı metnini veri sanır ve bozulur.

#### Kendi İletinizi Doğru Akışa Yazmak

`>&2` eki, bir komutun standart çıktısını standart hataya bağlar. Bir betikte uyarı
üretmenin doğru yolu budur:

```sh
uyar() { printf 'uyari: %s\n' "$1" >&2; }
uyar "kutuk bulunamadi"
echo "rapor gövdesi"
```

Bu iki satırın çıktısı, hangi akışa baktığınıza göre değişir. Yalnızca veriyi
istediğinizde:

```sh
uyar() { printf 'uyari: %s\n' "$1" >&2; }
{ uyar "kutuk bulunamadi"; echo "rapor gövdesi"; } 2>/dev/null
```

```
rapor gövdesi
```

Yalnızca tanı iletisini istediğinizde:

```sh
uyar() { printf 'uyari: %s\n' "$1" >&2; }
{ uyar "kutuk bulunamadi"; echo "rapor gövdesi"; } 1>/dev/null
```

```
uyari: kutuk bulunamadi
```

Kurs boyunca yazılacak betikte bu kural değişmeyecek: raporun kendisi standart çıktıya,
raporla ilgili her açıklama standart hataya gider.

#### Akışlar Bağımsız Tamponlanır

İki akışın ekranda göründüğü sıra, komutun onları yazdığı sıra olmayabilir. Ayrım,
akışların **bağımsız tamponlanmasından** gelir: standart hata genellikle tamponsuz veya
satır tamponludur, standart çıktı ise bir dosyaya ya da boruya bağlıyken blok tamponludur.
Blok tamponlu akış, tamponu dolana veya süreç bitene kadar bekletilir.

İki akışı aynı dosyaya toplayınca sonuç görülebilir:

```sh
ls erisim.log yok.txt > hepsi.txt 2>&1
cat hepsi.txt
```

```
ls: cannot access 'yok.txt': No such file or directory
erisim.log
```

Buradan çıkan kural: **iki akışın birbirine göre sırasına güvenmeyin.** Bir betiğin
çıktısını hata iletileriyle birlikte kütüğe yazıyorsanız, satırların kronolojik sırada
olacağını varsaymayın; sıra gerekiyorsa her satıra kendi zaman damgasını yazın.

#### Süzgeç Modeli

Standart girdi, akışların en az göze çarpanıdır çünkü etkileşimli kullanımda çoğu komut
onu kullanmaz. Oysa kabuğun bileşim gücü tam olarak buradan gelir.

**Süzgeç (filter)**, standart girdisinden okuyup standart çıktısına yazan komuttur.
Süzgeçler dosya adı bilmez, konum bilmez; yalnızca kendilerine akan metni dönüştürür.
`sort`, `tr`, `wc`, `head`, `grep` bu sınıftadır.

```sh
tr 'a-z' 'A-Z' <<< 'kabuk programlama'
```

```
KABUK PROGRAMLAMA
```

Komutlar girdi kaynağı bakımından üç öbeğe ayrılır:

1. **Yalnızca argümandan çalışanlar.** `mkdir`, `chmod`, `rm` gibi komutlar iş için
   dosya adına ihtiyaç duyar; standart girdiyi okumazlar.
2. **Yalnızca standart girdiden çalışanlar.** `tr` bunun tipik örneğidir; dosya adı
   argümanı almaz.
3. **İkisini de kabul edenler.** `sort`, `wc`, `grep`, `cat` gibi komutlar dosya adı
   verilirse onu, verilmezse standart girdiyi okur.

Üçüncü öbek en yaygınıdır ve iki farklı yazımı vardır:

```sh
wc -l erisim.log
```

```
30 erisim.log
```

```sh
wc -l < erisim.log
```

```
30
```

Fark yalnızca biçimsel değildir. İlk çağrıda `wc` dosyayı kendisi açar; adını bildiği
için çıktısına yazar. İkincisinde `wc` yalnızca bir akış görür; dosyanın adını bilmez,
dolayısıyla yazamaz. Ne zaman hangisinin istendiği duruma bağlıdır: çok dosyalı raporda
ad gerekir, sayıyı bir değişkene almak istediğinizde ad engeldir.

Satır sayısını hizalayarak yazan gerçekleştirimler sayının önüne boşluk koyar; macOS'taki
BSD `wc` böyledir, bu kursun hedefi olan GNU coreutils sürümü ise tek girdi için koymaz.
Sayıyı doğrudan karşılaştırmada kullanacaksanız bu boşluk sorun çıkarır — ilerleyen
derslerde aritmetik genişletme bu farkı soğurur.

Üçüncü öbekteki komutların çoğu, dosya adı yerine tek bir tire (`-`) kabul eder ve bunu
"standart girdi" diye yorumlar. Dosya listesinin ortasına akış sokmak gerektiğinde
kullanılır:

```sh
head -2 erisim.log | cat -n -
```

```
     1	10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
     2	10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /statik/stil.css HTTP/1.1" 200 2048
```

Tirenin bu anlamı çekirdek düzeyinde değil, her komutun kendi içinde tanımlıdır; genel
bir kural değil yaygın bir sözleşmedir.

#### Özet

- Her sürece üç akış devredilir: 0 numaralı standart girdi, 1 numaralı standart çıktı,
  2 numaralı standart hata. Program bu tanıtıcıların ucunda ne olduğunu bilmez.
- Komutun asıl ürünü olan veri 1'e, insana yönelik tanı iletileri 2'ye yazılır; kendi
  betiklerinizde uyarıları `>&2` ile ikinci akışa göndermek bu sözleşmeyi sürdürür.
- İki akış bağımsız tamponlandığı için birbirlerine göre sıraları güvence altında
  değildir.
- Süzgeç, standart girdisinden okuyup standart çıktısına yazan ve dosya adı bilmeyen
  komuttur; kabuğun bileşim gücü bu modelden gelir.
- Bir komuta dosya adı vermekle akış bağlamak eşdeğer değildir: adı bilen komut onu
  çıktısına da yazabilir.

#### Sonraki Adım

Bu derste akışların uçlarını `2>/dev/null` gibi gösterimlerle değiştirdik ama gösterimin
kendisini açıklamadık. Sonraki ders yönlendirme işleçlerini kuralıyla ele alır: dosyaya
yazma ile ekleme arasındaki fark, iki akışı tek dosyada toplamanın doğru sırası ve
yönlendirmelerin neden soldan sağa değerlendirildiği.

### Yönlendirme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/yonlendirme

Yönlendirme işleçleri, kesme ile ekleme farkı, tanıtıcı çoğaltma ve sıranın önemi, üzerine yazma tuzakları.

Önceki ders akışların uçlarının değiştirilebilir olduğunu gösterdi ama değiştirme
gösterimini kuralıyla açıklamadı. `2>/dev/null` yazıldığında kabuk tam olarak ne yapar,
`>` ile `>>` neden farklıdır, iki akışı tek dosyada toplarken sıra neden önemlidir?

Yönlendirme, kabuğun komutu çalıştırmadan **önce** yaptığı bir iştir. Komut başlamadan
tanıtıcılar bağlanır; komut, uçlarının değiştirildiğini hiç fark etmez. Bu dersin tüm
kuralları bu tek cümleden türer.

#### Kesme ve Ekleme

Standart çıktıyı dosyaya bağlayan iki işleç vardır.

`>` **kesme (truncate)** yapar: dosya yoksa yaratılır, varsa içeriği silinir. Yönlendirme
komut çalışmadan uygulandığı için, komut hiçbir şey yazmasa bile dosya boşalır.

```sh
echo "birinci" > not.txt
echo "ikinci" > not.txt
cat not.txt
```

```
ikinci
```

`>>` **ekleme (append)** yapar: yazma konumu her seferinde dosyanın sonuna alınır.

```sh
echo "birinci" > not.txt
echo "ikinci" >> not.txt
cat not.txt
```

```
birinci
ikinci
```

Kesmenin komuttan bağımsız olması, bir dosyayı boşaltmanın en kısa yolunu verir. `:`
yerleşik komutu hiçbir şey yapmaz ve hiçbir şey yazmaz; yönlendirme yine de uygulanır:

```sh
: > not.txt
wc -c not.txt
```

```
0 not.txt
```

Bir kütük dosyasını sıfırlamanın doğru yolu budur. Dosyayı silip yeniden yaratmak yerine
içeriğini boşaltmak, dosyayı açık tutan süreçlerin yazmaya devam etmesini sağlar; silinen
dosyanın tanıtıcısı geçerli kalır ama yazılanlar hiçbir dizinden erişilemez olur.

#### Girdi Yönlendirme

`<` işleci, standart girdiyi bir dosyaya bağlar. Önceki derste görüldüğü gibi bu, dosya
adını argüman olarak vermekle aynı şey değildir: komut yalnızca bir akış görür.

```sh
sort < erisim.log | head -2
```

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.0.12 - - [07/Feb/2024:09:13:02 +0000] "GET /urun/12 HTTP/1.1" 200 8410
```

Girdi yönlendirmesi dosyayı yalnızca okumaya açar; var olmayan bir dosya verilirse komut
hiç çalıştırılmaz ve kabuk hata bildirir.

#### Tanıtıcı Numarasıyla Yönlendirme

Yönlendirme işleçlerinin önüne yazılan sayı, hangi tanıtıcının bağlanacağını söyler.
Sayı yazılmazsa çıktı yönlendirmelerinde 1, girdi yönlendirmesinde 0 varsayılır.
Dolayısıyla `> dosya` ile `1> dosya` aynıdır.

```sh
ls erisim.log yok.txt >var.txt 2>hata.txt
cat var.txt
```

`var.txt` yalnızca veriyi alır:

```
erisim.log
```

`hata.txt` ise yalnızca tanı iletisini alır (`cat hata.txt`):

```
ls: cannot access 'yok.txt': No such file or directory
```

Sayı ile işleç arasında boşluk bulunamaz: `2 > hata.txt` yazılırsa kabuk `2` dizgisini
komuta argüman olarak geçirir ve yalnızca standart çıktıyı yönlendirir.

#### Tanıtıcı Çoğaltma ve Sıra

`n>&m` gösterimi yönlendirme değil **çoğaltmadır (duplication)**: `n` numaralı
tanıtıcı, `m` numaralının o an gösterdiği yeri gösterecek biçimde ayarlanır. Bu bir
bağlantı kopyalamadır; sonradan `m` değişse bile `n` eski yerde kalır.

Kural buradan çıkar: **yönlendirmeler soldan sağa uygulanır.** İki akışı aynı dosyada
toplamanın doğru yazımı önce hedefi belirlemek, sonra çoğaltmaktır.

```sh
ls erisim.log yok.txt >hepsi.txt 2>&1
cat hepsi.txt
```

```
ls: cannot access 'yok.txt': No such file or directory
erisim.log
```

Sıra ters çevrilirse sonuç değişir:

```sh
ls erisim.log yok.txt 2>&1 >ayri.txt
```

Bu çağrıda önce `2`, o anki `1`'in gösterdiği yere — yani terminale — bağlanır. Sonra
`1` dosyaya çevrilir, ama `2` terminalde kalmıştır. Terminale şu düşer:

```
ls: cannot access 'yok.txt': No such file or directory
```

`ayri.txt` ise yalnızca veriyi alır:

```
erisim.log
```

Bu, yönlendirmenin en sık yapılan hatasıdır. Yazılışları birbirine benzeyen iki
gösterimden biri "her şeyi dosyaya", diğeri "hataları eski yere, veriyi dosyaya"
anlamına gelir.

Bash, ilk durumun kısaltması olarak `&> dosya` yazımını da tanır. Kısa olmakla birlikte
POSIX kabuk dilinde tanımlı değildir; taşınabilir olması gereken betiklerde
`>dosya 2>&1` yazımı tercih edilir. Aynı biçimde `&>>` ekleme yapar.

#### İki Yere Birden Yazmak

Yönlendirme tek hedef alır. Çıktının hem dosyaya kaydedilmesi hem de bir sonraki komuta
akması gerektiğinde `tee` kullanılır: standart girdisinden okuduğunu hem verilen
dosyalara hem de standart çıktısına yazar.

```sh
head -2 erisim.log | tee ilk-iki.txt | wc -l
```

```
2
```

`ilk-iki.txt` içeriği:

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /statik/stil.css HTTP/1.1" 200 2048
```

`tee -a` dosyayı keserek değil ekleyerek yazar. Kurs sonunda yazılacak raporlama
betiğinde `tee`, hem ekrana hem kütüğe yazmak için kullanılacak.

#### Üzerine Yazma Koruması

`>` işlecinin var olan dosyayı sessizce silmesi, elle yazılan komutlarda veri kaybının
yaygın nedenidir. `noclobber` seçeneği bu davranışı kapatır:

```sh
# dene.sh
set -o noclobber
echo "a" > n.txt      # dosya yok, yaratılır
echo "b" > n.txt      # dosya var, reddedilir
```

```
dene.sh: line 4: n.txt: cannot overwrite existing file
```

Bu ileti bloğun betik olarak koşturulmasının çıktısıdır: ileti betiğin adıyla ve hatanın
oluştuğu satır numarasıyla başlar. Aynı komutu etkileşimli kabukta yazarsanız önek `bash:`
olur.

Koruma açıkken bilinçli üzerine yazma `>|` ile yapılır:

```sh
echo "c" >| n.txt
cat n.txt
```

```
c
```

`noclobber` etkileşimli oturum için yararlıdır; betiklerde ise güvenilmez bir dayanaktır,
çünkü betiği çalıştıran kabuğun ayarına bağlıdır. Betikte üzerine yazmayı engellemek
istiyorsanız dosyanın varlığını açıkça sınayın — koşul sınamaları Betik Yapısı konusunda
ele alınacak.

#### Aynı Dosyayı Okuyup Yazmak

Yönlendirmenin komuttan önce uygulanması, tek bir kuralı kaçınılmaz kılar: **bir komutun
girdi dosyası ile çıktı dosyası aynı olamaz.**

```sh
printf 'c\nb\na\n' > kucuk.txt
sort kucuk.txt > kucuk.txt
wc -c kucuk.txt
```

```
0 kucuk.txt
```

Dosya boşaldı. Kabuk `kucuk.txt` dosyasını kesti, sonra `sort` çalıştı ve okuyacak
hiçbir şey bulamadı. Veri geri getirilemez.

Bu yüzden yerinde dönüştürme yapan araçlar (akış editörünün `-i` seçeneği gibi) dosyayı
doğrudan değiştirmez; geçici dosyaya yazıp sonunda yer değiştirir. Aynı işi elle yapmak
gerektiğinde izlenecek desen şudur:

```sh
sort kucuk.txt > kucuk.yeni && mv kucuk.yeni kucuk.txt
```

`&&` işleci, soldaki komut başarılı bitmediyse sağdakini çalıştırmaz; böylece `sort`
hata verdiğinde özgün dosya yerinde kalır. Bu işlecin tam anlamı bu konunun son dersinde
tanımlanacak.

#### Betiğin Tümünü Yönlendirmek

`exec` yerleşik komutu, komut adı verilmeden yalnızca yönlendirmelerle çağrılırsa,
yönlendirmeleri kabuğun kendisine uygular. O noktadan sonraki tüm komutlar yeni
uçları kullanır.

```sh
exec 3>&1 1>kayit.txt    # 1'in eski yerini 3'te sakla, 1'i dosyaya çevir
echo "dosyaya"
exec 1>&3 3>&-           # eski yeri geri yükle, 3'ü kapat
echo "ekrana"
```

Terminale yalnızca ikinci satır düşer:

```
ekrana
```

`kayit.txt` içeriği:

```
dosyaya
```

`3>&-` gösterimi tanıtıcıyı kapatır. Üç ve üzeri numaralı tanıtıcılar bu tür geçici
saklama için serbesttir; kabuk onlara anlam yüklemez. Bu desen, bir betiğin tüm
çıktısını kütüğe alıp yalnızca özeti terminale döndürmek için kullanılır ve kursun
sonunda zamanlanmış görev yazarken işe yarayacak.

#### Özet

- Yönlendirme komut çalışmadan önce uygulanır; komut, tanıtıcılarının nereye bağlandığını
  bilmez.
- `>` hedefi keser, `>>` sonuna ekler; kesme komuttan bağımsız olduğu için `: > dosya`
  bir dosyayı boşaltır.
- İşlecin önündeki sayı hangi tanıtıcının bağlanacağını belirler; `n>&m` çoğaltmadır ve
  yönlendirmeler soldan sağa uygulandığı için `>dosya 2>&1` ile `2>&1 >dosya` farklı
  anlamlara gelir.
- Tek çıktının iki hedefe gitmesi gerektiğinde `tee` kullanılır.
- Bir komutun girdi ve çıktı dosyası aynı olamaz; doğru desen geçici dosyaya yazıp
  yer değiştirmektir.

#### Sonraki Adım

Yönlendirme, akışın ucuna bir dosya koyar. Asıl bileşim gücü ise ucuna başka bir komut
konduğunda ortaya çıkar. Sonraki ders boru hattını ele alır: komutların eşzamanlı
çalışması, verinin arada dosyaya uğramadan akması ve bu düzenin getirdiği iki tuzak —
zincirin çıkış kodu ve alt kabukta kaybolan değişkenler.

### Boru Hattı

Source: https://academia.sh/tr/kurslar/kabuk-programlama/boru-hatti

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.

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:

```sh
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:

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
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.

```sh
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:

```sh
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:

```sh
ls erisim.log yok.txt 2>&1 | sort
```

```
erisim.log
ls: cannot access '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ç `SIGPIPE` ile
  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 `PIPESTATUS` veya
  `pipefail` ile 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.

### Burada Belge ve Burada Dizgi

Source: https://academia.sh/tr/kurslar/kabuk-programlama/burada-belge-ve-burada-dizgi

Betiğe gömülü girdi, sonlandırıcının alıntılanmasının genişletmeye etkisi, girinti kaldırma ve tek satırlık girdi gösterimi.

Önceki üç ders girdinin iki kaynağını gösterdi: klavye ve dosya. Betikler üçüncü bir
kaynağa daha ihtiyaç duyar — betiğin kendi metnine gömülü sabit içerik. Yapılandırma
şablonu, çok satırlı kullanım iletisi, sınama verisi ve veritabanı sorgusu bu sınıfa
girer.

Bu içeriği ayrı bir dosyada tutmak, betiği tek başına taşınamaz kılar. Her satırı ayrı
`echo` ile yazmak ise satır sonlarını, tırnakları ve boşlukları elle yönetmeyi gerektirir.
Kabuk bu iş için iki gösterim tanımlar.

#### Burada Belge

**Burada belge (here document)**, `<<` işlecinden sonra bir **sonlandırıcı** yazılarak
başlar; sonlandırıcının tek başına geçtiği satıra kadarki metin, komutun standart
girdisi olur.

```sh
kod=404
cat <<SON
Aranan kod: $kod
SON
```

```
Aranan kod: 404
```

Sonlandırıcı adı serbesttir; içerikte geçmeyecek bir dizi seçilir. `EOF` yaygın bir
seçimdir, ama zorunlu değildir — bu kursta `SON` kullanılacak.

İki kural katıdır. Sonlandırıcı satırında ondan başka hiçbir karakter bulunamaz;
sondaki tek bir boşluk bile satırı sonlandırıcı olmaktan çıkarır ve kabuk dosya sonuna
kadar okumaya devam ederek hata verir. İkincisi, sonlandırıcı satırı girintili olamaz —
bunun bir istisnası aşağıda ele alınıyor.

#### Genişletme ve Sonlandırıcının Alıntılanması

Burada belge içeriği, öntanımlı olarak çift tırnak içindeymiş gibi işlenir: değişken
genişletme, komut yerine koyma ve aritmetik genişletme uygulanır.

```sh
ad="rapor"
cat <<SON
Kutuk: $ad.log
Satir sayisi: $(grep -c '' erisim.log)
Aritmetik: 5 * 3 = $((5 * 3))
SON
```

```
Kutuk: rapor.log
Satir sayisi: 30
Aritmetik: 5 * 3 = 15
```

Sonlandırıcı **alıntılanırsa** hiçbir genişletme yapılmaz; metin harfi harfine geçer.
Alıntılama tek tırnak, çift tırnak veya ters bölü ile yapılabilir; üçü de aynı sonucu
verir.

```sh
cat <<'SON'
Kutuk: $ad.log
Komut: $(pwd)
Aritmetik: $((5 * 3))
SON
```

```
Kutuk: $ad.log
Komut: $(pwd)
Aritmetik: $((5 * 3))
```

Bu ayrım yazım süsü değil, doğruluk koşuludur. Bir betiğe başka bir betiğin kaynak kodunu,
bir kabuk yapılandırmasını veya `$` içeren herhangi bir şablonu gömerken sonlandırıcı
alıntılanmazsa, gömülü metindeki `$` işaretleri dış betikte genişletilir ve içerik sessizce
bozulur. Kural olarak: **genişletme bilinçli olarak isteniyorsa sonlandırıcı açık
bırakılır, aksi hâlde alıntılanır.**

#### Girinti Kaldırma

Burada belge, bir `if` veya `for` gövdesinin içindeyken çevresindeki kodun girintisine
uymaz; içerik harfi harfine aktarıldığı için girinti de metne girer. `<<-` biçimi bu
sorunu kısmen çözer: içerik satırlarının ve sonlandırıcı satırının **başındaki sekme
karakterlerini** siler.

```sh
if true; then
	cat <<-SON
		girintili satir
		ikinci satir
	SON
fi
```

```
girintili satir
ikinci satir
```

Kısıt açıktır: yalnızca sekme karakteri silinir, boşluk silinmez. Boşlukla girintileyen
bir dosyada `<<-` hiçbir işe yaramaz. Bu, kabuk dilinin taşınabilir tutulmuş ama
kullanışsız kalmış köşelerinden biridir; yazım kurallarınız boşluk girintisi
gerektiriyorsa burada belgeyi girintisiz bırakmak daha az sürprizlidir.

#### Burada Dizgi

**Burada dizgi (here string)**, tek satırlık girdi için kısaltmadır. `<<<` işlecinin
sağındaki değer, sonuna bir satır sonu eklenerek komutun standart girdisi olur.

```sh
ad="rapor"
tr 'a-z' 'A-Z' <<< "$ad hazir"
```

```
RAPOR HAZIR
```

Eklenen satır sonu gerçektir ve sayılır:

```sh
wc -c <<< "abc"
printf '%s' "abc" | wc -c
```

```
4
3
```

Bu fark, uzunluk sayan veya baytı baytına karşılaştıran işlerde önemlidir. Satır sonu
istemiyorsanız burada dizgi değil, `printf` ile boru hattı kullanın.

Burada dizginin en yaygın kullanımı, bir dizgiyi alanlarına ayırmaktır:

```sh
read -r adres kod <<< "10.0.0.12 200"
echo "adres=$adres kod=$kod"
```

```
adres=10.0.0.12 kod=200
```

Aynı işi boru hattıyla yapmak, önceki dersteki alt kabuk tuzağına düşerdi:
`echo ... | read` yazımında `read` alt kabukta çalışır ve atadığı değişkenler kaybolur. Burada dizgi
yönlendirmedir, boru değildir; komut ana kabukta kalır. `<<<` bash eklentisidir, POSIX
kabuk dilinde yoktur.

#### Boru Hattında ve Yönlendirmeyle

Burada belge de sıradan bir girdi yönlendirmesidir; komutun geri kalan yönlendirmeleriyle
ve boru hattıyla birlikte kullanılabilir. Yönlendirmeler komut satırının sonunda toplanır,
içerik ise komutu izleyen satırlardan başlar:

```sh
sort <<'SON' | head -2
gamma
alpha
beta
SON
```

```
alpha
beta
```

Çıktının hedefe yazılması da aynı satırda belirtilir. Bu, sabit içeriği dosyaya dökmenin
en doğrudan yoludur:

```sh
cat > mini.log <<'SON'
10.0.0.1 GET /a 200
10.0.0.2 GET /b 404
SON
```

```sh
wc -l mini.log
```

```
2 mini.log
```

#### Kütüğü Yeniden Üretmek

Bu kursun `erisim.log` dosyası bir dış kaynaktan indirilmez; betikle üretilir. Böylece
her okur aynı veriyle çalışır ve sonuçlar birebir karşılaştırılabilir olur.

Aşağıdaki `veri-uret.sh` betiği kütüğü sıfırdan yazar. Sonlandırıcı alıntılanmıştır:
içerikte `$` geçmese bile, veri metnini genişletmeye açık bırakmamak doğru varsayılandır.

```sh
cat > veri-uret.sh <<'BETIK_SONU'
cat > erisim.log <<'SON'
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /statik/stil.css HTTP/1.1" 200 2048
SON
BETIK_SONU
```

Dıştaki sonlandırıcı (`BETIK_SONU`) ile içteki (`SON`) farklı seçilmiştir. Aynı olsalardı
dış belge ilk `SON` satırında biterdi ve geri kalan metin komut olarak yorumlanırdı. İç
içe burada belge yazarken sonlandırıcıların ayrı olması zorunludur.

Yukarıdaki parça, kütüğün otuz satırından yalnızca ilk ikisini içerir; otuz satırın
tamamını taşıyan betik Standart Girdi, Çıktı ve Hata dersinde verilmişti. Oradan
kaydettiğiniz `veri-uret.sh` sonraki derslerde kütük bozulduğunda `bash veri-uret.sh`
tek komutuyla onu geri getirir. Parçayı denerseniz aynı adlı betiğinizin üzerine yazar;
tam betiği kaybetmemek için onu ayrı bir dizinde deneyin.

#### Ne Zaman Hangisi

Üç gösterim aynı işi farklı ölçeklerde yapar:

| Gösterim | Girdi | Genişletme | Tipik kullanım |
|---|---|---|---|
| `<< SON` | çok satırlı | uygulanır | şablon, biçimlendirilmiş rapor başlığı |
| `<< 'SON'` | çok satırlı | uygulanmaz | gömülü kaynak kod, sabit veri |
| `<<<` | tek satırlı | uygulanır | dizgiyi alanlara ayırma, tek satır süzme |

Dördüncü bir seçenek olarak `printf` ile boru hattı her zaman kullanılabilir ve
tamamen taşınabilirdir; karşılığında satır sonlarının elle yönetilmesini ister.

#### Özet

- Burada belge, `<<` ile başlar ve sonlandırıcının tek başına geçtiği satıra kadarki
  metni komutun standart girdisi yapar.
- Sonlandırıcı alıntılanmazsa içerik çift tırnak kuralıyla genişletilir; alıntılanırsa
  harfi harfine geçer. Gömülü kaynak kodda alıntılamak zorunludur.
- `<<-` biçimi yalnızca sekme girintisini kaldırır; boşluk girintisine etkisi yoktur.
- Burada dizgi tek satırlık girdi verir ve sonuna satır sonu ekler; boru hattının aksine
  komutu alt kabuğa itmez.
- İç içe burada belgelerde sonlandırıcılar farklı seçilmelidir.

#### Sonraki Adım

Bu konudaki tüm örneklerde komutların başarılı olduğu varsayıldı. Oysa `grep` eşleşme
bulamadığında, `cut` dosyayı açamadığında ya da `sort` disk alanı bulamadığında bir
sonuç bildirir. Sonraki ders bu bildirimi — çıkış kodunu — tanımlar: sıfırın neden başarı
demek olduğunu, `&&` ve `||` işleçlerinin bu koda nasıl bağlandığını ve kabuğun kendi
ayırdığı kod aralıklarını ele alır.

### Çıkış Kodları

Source: https://academia.sh/tr/kurslar/kabuk-programlama/cikis-kodlari

Başarı/başarısızlık sözleşmesi, ayrılmış kod aralıkları, koşullu zincirleme işleçleri ve bu işleçlerin koşullu dallanmayla karıştırılmaması.

Önceki derslerde komutların sonuçları hep beklendiği gibi çıktı. Gerçekte `grep` eşleşme
bulamayabilir, `cut` dosyayı açamayabilir, `sort` yeterli geçici alan bulamayabilir.
Kabuğun bu durumları öğrenme yolu tek bir sayıdır.

Her süreç sonlanırken çekirdeğe bir tam sayı bırakır. Kabuk bu sayıyı **çıkış kodu (exit
status)** olarak alır ve `$?` özel değişkeninde saklar. Kabuk programlamanın denetim
akışı — koşullar, döngü koşulları, hata yönetimi — tümüyle bu sayının üzerine kuruludur.

#### Sıfır Neden Başarıdır

Sözleşme şudur: **0 başarı, sıfırdan farklı her değer başarısızlıktır.**

Programlama Temelleri kursunda mantıksal doğruluk için `true` değeri kullanılmıştı;
kabuktaki sözleşme bunun tersi görünür. Gerekçe basittir: bir işin başarılı olmasının
tek bir anlamı vardır, başarısız olmasının ise pek çok nedeni. Sıfırdan farklı 255
değer, farklı başarısızlık türlerini ayırt etmek için kullanılabilir; başarı için tek
bir değer yeterlidir.

`grep` bu ayrımın açık bir örneğidir ve üç kodu birbirinden ayırır:

```sh
grep -q '404' erisim.log;      echo "bulundu    -> $?"
grep -q '999' erisim.log;      echo "bulunamadi -> $?"
grep -q '404' yok-dosya.log 2>/dev/null; echo "dosya yok  -> $?"
```

```
bulundu    -> 0
bulunamadi -> 1
dosya yok  -> 2
```

Üçü de birbirinden farklıdır ve bu fark iş görür. "Eşleşme yok" beklenen bir sonuçtur;
"dosya yok" ise betiğin durması gereken bir durumdur. Yalnızca "sıfır mı değil mi" diye
bakan bir betik bu ikisini karıştırır.

Kendi betiklerinizde de aynı titizlik beklenir: farklı hata sınıflarına farklı kodlar
verin, `1` kodunu her şey için kullanmayın.

#### Sözleşmeyi Somutlaştıran Komutlar

`true` ve `false` yerleşik komutları hiçbir iş yapmaz; yalnızca kod döndürür.

```sh
true;  echo "true  -> $?"
false; echo "false -> $?"
```

```
true  -> 0
false -> 1
```

`:` komutu `true` ile eşdeğerdir ve Yönlendirme dersinde dosya boşaltmak için
kullanılmıştı.

`test` komutu ve onun köşeli ayraç yazımı `[ ... ]` bir koşulu sınar ve sonucu kod olarak
döndürür. Bu komutun hiçbir çıktısı yoktur; tüm bilgi koddadır.

```sh
[ -f erisim.log ]; echo "dosya var -> $?"
[ -f yok.log ];    echo "dosya yok -> $?"
```

```
dosya var -> 0
dosya yok -> 1
```

`[` bir sözdizim öğesi değil, çalıştırılabilir bir komut adıdır; bu yüzden çevresindeki
boşluklar zorunludur ve kapanış `]` onun son argümanıdır. Testlerin ayrıntısı Koşullar ve
Testler dersinde ele alınacak.

#### Kodun Sayısal Sınırları

Çıkış kodu 0–255 aralığında bir bayta sığar. Bu aralığın dışındaki değerler 256'ya göre
indirgenir:

```sh
(exit 300); echo "300 -> $?"
(exit -1);  echo "-1  -> $?"
```

```
300 -> 44
-1  -> 255
```

$300 \bmod 256 = 44$ ve $-1 \bmod 256 = 255$. Betiğinizde 255'ten büyük bir kod
döndürmenin anlamı yoktur; sayı sessizce başka bir şeye dönüşür.

#### Ayrılmış Kodlar

Kabuk bazı kodları kendi kullanımı için ayırır. Bu kodları kendi hata sınıflarınıza
vermek, betiğinizin çağıranı yanıltmasına yol açar.

```sh
echo metin > calismaz.txt    # çalıştırma izni olmayan bir dosya
yokboylebirkomut 2>/dev/null; echo "bulunamayan komut  -> $?"
./calismaz.txt   2>/dev/null; echo "calistirilamayan   -> $?"
```

```
bulunamayan komut  -> 127
calistirilamayan   -> 126
```

`127`, komutun arama yolunda bulunamadığını; `126`, bulunduğunu ama çalıştırılamadığını
(çalıştırma izni yok ya da dizin) bildirir. Bir betiği çağırdığınızda 127 alıyorsanız
sorun betiğin içinde değil, adında veya arama yolundadır.

Sinyalle sonlanan süreçler için kod **128'den büyük** olur; yaygın kabuklarda bu kod
**128 + sinyal numarası**dır, ama standart yalnız 128'den büyük olmasını şart koşar:

```sh
bash -c 'trap - INT; kill -INT $$'; echo "INT  -> $?"
bash -c 'kill -TERM $$';            echo "TERM -> $?"
```

```
INT  -> 130
TERM -> 143
```

`SIGINT` 2 numaralıdır ($128 + 2 = 130$), `SIGTERM` 15 ($128 + 15 = 143$). Klavyeden
kesilen bir betiğin 130 döndürmesinin nedeni budur. Sinyaller ve yakalanmaları Sağlam
Betik Yazımı konusunda ayrıntılı ele alınacak.

Kullanılabilir aralık pratikte 1–125'tir. `exit` komutu argümansız çağrılırsa, betikte
çalışan son komutun kodunu döndürür.

#### Koşullu Zincirleme

Kabuk, çıkış koduna bağlı iki zincirleme işleci tanımlar.

`&&` işleci, solundaki komut **0 döndürürse** sağdakini çalıştırır. `||` işleci,
solundaki komut **sıfırdan farklı** döndürürse sağdakini çalıştırır. Her iki durumda da
sağdaki komut çalışmazsa zincirin kodu soldakinin kodudur.

```sh
grep -q '404' erisim.log && echo "kutukte 404 var"
grep -q '999' erisim.log || echo "999 gecmiyor"
```

```
kutukte 404 var
999 gecmiyor
```

Bu işleçler **kısa devre değerlendirme (short-circuit evaluation)** yapar — Programlama
Temelleri kursunda mantıksal işleçler için tanımlanan davranışın aynısıdır. Fark, burada
değerlendirilenin bir ifade değil, çalıştırılan bir süreç olmasıdır: kısa devre yalnızca
zaman kazandırmaz, bir işin hiç yapılmamasını sağlar.

Yönlendirme dersinde güvenli yerinde dönüştürme deseni bu işleçle yazılmıştı:

```sh
printf 'c\nb\na\n' > kucuk.txt
sort kucuk.txt > kucuk.yeni && mv kucuk.yeni kucuk.txt
```

`sort` başarısız olursa `mv` hiç çalışmaz ve özgün dosya yerinde kalır. Yıkıcı işlemi
başarı koşuluna bağlamak, kabukta hata yönetiminin en yalın biçimidir.

#### Zincirleme Koşullu Dallanma Değildir

`&&` ve `||` **aynı önceliğe** sahiptir ve **soldan sağa** birleşir. Bu iki kural, sık
görülen bir yanlış anlamayı doğurur: `koşul && gövde || yedek` yazımının `if`–`else`
ile eşdeğer olduğu sanılır. Değildir.

```sh
yanlis() { echo "govde calisti"; return 1; }

true && yanlis || echo "YEDEK CALISTI"
```

```
govde calisti
YEDEK CALISTI
```

Koşul doğruydu, gövde çalıştı — ama gövde başarısızlık bildirdiği için yedek de çalıştı.
`if`–`else` böyle davranmaz:

```sh
yanlis() { echo "govde calisti"; return 1; }
if true; then yanlis; else echo "YEDEK"; fi
```

```
govde calisti
```

Kural: **`&&` … `||` zinciri yalnızca gövdenin asla başarısız olmayacağı durumda `if`–`else`
yerine geçer.** Bu güvence nadiren verilebildiği için, iki dallı seçimde `if` yazmak
doğru varsayılandır. `&&` ve `||`, tek dallı korumalar için — "başarılıysa devam et",
"başarısızsa uyar ve çık" — uygundur.

Soldan sağa birleşme, karışık zincirlerde de kendini gösterir:

```sh
false || echo "birinci" && echo "ikinci"
```

```
birinci
ikinci
```

`false || echo "birinci"` önce değerlendirilir; `echo` başarılı olduğu için zincirin kodu
0'dır ve `&&` sağdakini çalıştırır. Okunması güç zincirler yerine gruplama kullanın:
`{ ...; }` içindeki komut listesinin kodu, listenin son komutunun kodudur.

#### Değilleme

`!` öneki, bir komutun kodunu tersine çevirir: 0 ise sıfırdan farklı, sıfırdan farklı ise
0 yapar.

```sh
! grep -q '999' erisim.log; echo "degilleme -> $?"
```

```
degilleme -> 0
```

"Kütükte 999 geçmiyorsa" koşulunu ifade etmenin doğrudan yolu budur. Değillemenin ikinci
bir işlevi de vardır: katı kip altında (`set -e`) beklenen başarısızlıkları
bastırmak. Bu kullanım Hata Yönetimi dersinde ele alınacak.

#### Konunun Toparlanması

Bu konuda kurulan dört kavram, kursun geri kalanının sözlüğünü oluşturur. Kütükten en çok
istenen beş yolu çıkaran hat, bu sözlüğün tamamını kullanır:

```sh
[ -r erisim.log ] || { echo 'kutuk okunamiyor' >&2; exit 2; }
cut -d' ' -f7 erisim.log | sort | uniq -c | sort -rn | head -5
```

Birinci satır bir çıkış kodu sınaması, bir zincirleme işleci, bir gruplama ve standart
hataya yönlendirme içerir. İkinci satır beş komutluk bir boru hattıdır. İkisi birlikte,
sonraki konuda yazılacak `rapor.sh` betiğinin çekirdeğidir.

#### Özet

- Her süreç sonlanırken 0–255 aralığında bir çıkış kodu bırakır; 0 başarı, sıfırdan farklı
  her değer başarısızlıktır. Kod `$?` ile okunur.
- Farklı başarısızlık türleri farklı kodlarla ayrılır; `grep` eşleşmesizlik için 1, dosya
  hatası için 2 döndürür.
- 126, 127 ve 128 + sinyal numarası kabuk tarafından ayrılmıştır; kendi kodlarınız için
  1–125 aralığı kullanılır.
- `&&` ve `||` kısa devre değerlendirme yapar; aynı öncelikte olup soldan sağa birleşir.
- `koşul && gövde || yedek` yazımı, gövdenin başarısız olabildiği durumda `if`–`else` ile
  eşdeğer değildir.

#### Sonraki Adım

Bu konudaki her şey tek satırlık komutlarla yapıldı. Aynı hattı her gün çalıştırmak,
üzerine seçenek eklemek ve başkasına devretmek gerektiğinde tek satır yetmez. Sonraki
konu betik dosyasını kurar: yorumlayıcı bildiriminin ne işe yaradığını, betiğin nasıl
çalıştırılabilir hâle geldiğini ve kabuğun bir dosyayı yürütürken hangi kabuğu
kullandığını ele alarak başlar.

## Betik Yapısı

Yorumlayıcı bildiriminden değişken genişletmeye, tırnak kurallarından koşul, döngü, fonksiyon ve dizilere kabuk betiğinin yapı taşları.

### Betik Anatomisi

Source: https://academia.sh/tr/kurslar/kabuk-programlama/betik-anatomisi

Yorumlayıcı bildirimi, çalıştırma izni, betiği çalıştırmanın üç yolu ve alt süreçle kaynak okuma arasındaki fark.

Önceki konu, komutları akışlarla ve çıkış kodlarıyla birbirine bağlamayı kurdu. Ortaya
çıkan hatlar tek satırlıktı: yazıldıkları anda çalıştılar ve kayboldular. Aynı hattı her
gün çalıştırmak, üzerine seçenek eklemek ve başkasına devretmek gerektiğinde tek satır
yetmez.

**Betik (script)**, kabuğun okuyup sırayla yürüttüğü komutları tutan sıradan bir metin
dosyasıdır. Bu ders dosyanın ne olduğunu, çekirdeğin onu nasıl çalıştırdığını ve üç farklı
çalıştırma yolunun süreç açısından ne anlama geldiğini ele alır.

#### İlk Betik

Önceki dersin son hattı bir dosyaya alınır:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.

echo "== En cok istenen yollar =="
cut -d' ' -f7 erisim.log | sort | uniq -c | sort -rn | head -5
```

Bu dosya `rapor.sh` adıyla kaydedilir. Kurs boyunca her konu bu betiği bir adım
ilerletecek; sonunda argüman alan, hataya dayanıklı ve zamanlanmış olarak çalışan bir
araç hâline gelecek.

`#` ile başlayan satırlar **yorumdur**; kabuk onları yok sayar. Yorum işareti satır
başında olmak zorunda değildir ama kelime ortasında yorum başlatmaz:

```sh
echo abc#def
echo abc #def
```

```
abc#def
abc
```

Yorumun başlaması için `#` işaretinin bir kelimenin başında bulunması gerekir. Bu, dosya
adlarında ve URL'lerde `#` geçtiğinde işe yarayan bir kuraldır.

#### Çalıştırma İzni

Betik dosyası yaratıldığında çalıştırma izni taşımaz. Doğrudan çağrılırsa:

```sh
# dene.sh
./rapor.sh
echo $?
```

```
dene.sh: line 2: ./rapor.sh: Permission denied
126
```

Hata iletisinin başındaki `dene.sh: line 2:` öneki, komutun bir betiğin içinden
çağrıldığını gösterir: betiğin adı ve satır numarası yazılır. Aynı komut etkileşimli
kabukta yazılsaydı önek `bash:` olurdu. Kod `126`, "dosya bulundu ama çalıştırılamadı" anlamına gelir; önceki derste ayrılmış
kodlar arasında geçmişti. İzin verilir:

```sh
chmod +x rapor.sh
./rapor.sh
```

```
== En cok istenen yollar ==
      6 /api/veri
      4 /yok.html
      4 /index.html
      3 /urun/45
      3 /urun/12
```

Betiğin adının önündeki `./` bırakılırsa sonuç değişir:

```sh
# dene.sh
rapor.sh
echo $?
```

```
dene.sh: line 2: rapor.sh: command not found
127
```

Linux'a Giriş kursunun komut yolu dersinde kurulan kural burada işler: eğik çizgi
içermeyen bir ad, yalnızca `PATH` içindeki dizinlerde aranır ve bulunulan dizin bu
listede yer almaz. `./rapor.sh` yazımı adı bir yol hâline getirir ve arama yapılmaz.

Bulunulan dizini `PATH` listesine eklemek yaygın bir öneri değildir: o dizine `ls` adında
bir dosya bırakan herkes, orada çalıştırdığınız her komutu ele geçirebilir.

#### Yorumlayıcı Bildirimi

Betiğin ilk satırındaki `#!` dizisi bir yorum gibi görünür ama işlevi kabuğun değil
çekirdeğin ilgi alanındadır. Dosyanın ilk iki baytı `#!` ise, çekirdek onu doğrudan
çalıştırmaya kalkmaz; satırın kalanını bir program yolu olarak okur, o programı başlatır
ve betik dosyasının yolunu ona argüman olarak verir.

Bu düzeneğe **yorumlayıcı bildirimi (shebang)** denir. Üç ayrıntısı vardır.

**Yalnızca ilk satırda geçerlidir.** İkinci satırdaki `#!` sıradan yorumdur. İlk satırın
ilk iki baytı `#!` değilse bildirim yoktur.

**Yol saltıktır, arama yapılmaz.** Çekirdek `PATH` bakmaz. `#!/bin/bash` yazan bir betik,
bash'in `/bin/bash` yolunda bulunmadığı bir sistemde çalışmaz.

**Bildirim yoksa davranış çağırana kalır.** Çekirdek dosyayı çalıştıramaz ve hata
döndürür; bunun üzerine çağıran kabuk çoğu durumda dosyayı kendi alt kabuğuna okutur.
Sonuç, betiği çalıştıran kabuğa bağlıdır — yani taşınabilir değildir.

```sh
printf 'echo "shebangsiz calisti: $0"\n' > yalin.sh
chmod +x yalin.sh
./yalin.sh
```

```
shebangsiz calisti: ./yalin.sh
```

Çalıştı, ama hangi kabukla çalıştığı garanti değildir. Her betiğe yorumlayıcı bildirimi
yazmak zorunlu bir alışkanlıktır.

#### Hangi Yorumlayıcı

İki yaygın yazım vardır ve seçim betiğin ne kullandığına bağlıdır.

`#!/bin/sh`, POSIX kabuk dilini bildirir. Bu yolun ucundaki programın hangi kabuk olduğu
sisteme göre değişir; verilen tek güvence, POSIX kabuk dilini yorumlayacağıdır. Dizi,
`[[ ]]`, `<<<`, `${var,,}` gibi bash eklentileri bu bildirimle kullanılamaz — bazı
sistemlerde çalışıyor görünmeleri yanıltıcıdır, çünkü orada `/bin/sh` bash'in POSIX
kipidir.

`#!/usr/bin/env bash`, `env` komutunu çağırarak bash'i `PATH` içinde arar. Bash'in
sistemden sisteme farklı yerlerde kurulu olması bu yazımla soğurulur. Bedeli, `PATH`
üzerinden dolaylı bir arama yapılmasıdır; sabit yol gerektiren sıkı ortamlarda
`#!/bin/bash` yeğlenir.

Karar kuralı: **betikte bash'e özgü tek bir özellik kullanıyorsanız bash bildirin.**
POSIX bildirip bash özelliği kullanmak, betiğin başka bir sistemde sessizce bozulmasının
en sık nedenidir. Bu kursta bash'e özgü her özellik metinde açıkça belirtilecek.

Bash sürümleri arasında da fark vardır. Bir sistemde `/bin/bash` eski bir ana sürüm
olabilir; ilişkisel diziler gibi özellikler orada yoktur. Diziler ve İlişkisel Diziler
dersinde bu farkın ölçülebilir bir örneği verilecek.

#### Çalıştırmanın Üç Yolu

Aynı betik üç farklı biçimde yürütülebilir ve üçü aynı şey değildir.

**Doğrudan çalıştırma** (`./rapor.sh`) çalıştırma izni ve yorumlayıcı bildirimi ister.
Yeni bir süreç yaratılır.

**Yorumlayıcıya argüman olarak verme** (`bash rapor.sh`) ne izin ne bildirim ister;
bildirim varsa da yok sayılır, çünkü dosya çalıştırılmaz, okunur. Yine yeni bir süreç
yaratılır.

**Kaynak okuma** (`. rapor.sh` veya `source rapor.sh`) yeni süreç yaratmaz. Komutlar
o anki kabuğun içinde yürütülür.

Üçüncüsünün farkı gözlemlenebilir. `ayar.sh` yalnızca bir değişken atasın:

```sh
# ayar.sh
KUTUK=erisim.log
```

```sh
bash ayar.sh
echo "alt surec sonrasi: [${KUTUK-tanimsiz}]"
. ./ayar.sh
echo "source sonrasi: [${KUTUK-tanimsiz}]"
```

```
alt surec sonrasi: [tanimsiz]
source sonrasi: [erisim.log]
```

Alt süreçte yapılan atama, süreçle birlikte kayboldu. Boru hattı dersindeki alt kabuk
davranışının aynısıdır: **süreçler arasında değişken geçmez.** Kaynak okuma bu yüzden
yapılandırma dosyaları ve fonksiyon kitaplıkları için kullanılır; iş yapan betikler için
uygun değildir, çünkü betiğin `exit` çağrısı çağıran kabuğu kapatır.

`.` yazımı POSIX'tir, `source` bash eklentisidir; ikisi aynı işi yapar. POSIX yazımında
dosya adı eğik çizgi içermiyorsa `PATH` içinde aranır — `. ./ayar.sh` yazımındaki `./`
bu yüzden gereklidir.

#### Betiğin Çıkış Kodu

Bir betik, son çalıştırdığı komutun kodunu döndürür. Aşağıdaki betiğin son satırı
eşleşme bulamayan bir `grep` ise:

```sh
#!/usr/bin/env bash
# sonkod.sh
grep -q '999' erisim.log
```

```sh
chmod +x sonkod.sh
./sonkod.sh; echo "kod=$?"
```

```
kod=1
```

Betik başarılı çalıştı ama başarısızlık bildirdi. Bu davranış istenmiyorsa kod açıkça
yazılır: `exit 0`. Çıkış kodunu bilinçli belirlemek, betiği bir zincirde ya da
zamanlanmış görevde kullanılabilir kılan şeydir.

#### Çalıştırmadan Sözdizim Denetimi

`bash -n` seçeneği betiği okur, ayrıştırır ama çalıştırmaz. Yarım kalmış bir yapı varsa
bildirir:

`if` açıp `fi` ile kapatmayı unutan bir `bozuk.sh` dosyası olsun:

```sh
# bozuk.sh
if [ -f erisim.log ]; then
  echo "kutuk var"
```

```sh
bash -n bozuk.sh
```

```
bozuk.sh: line 4: syntax error: unexpected end of file
```

Bu denetim yalnızca sözdizimini görür; var olmayan komutları, yanlış seçenekleri veya
mantık hatalarını yakalamaz. Yine de yıkıcı işlem içeren bir betiği ilk kez
çalıştırmadan önce yapılacak en ucuz sınamadır. Hata ayıklama araçlarının tamamı Betik
Hata Ayıklama dersinde ele alınacak.

#### Özet

- Betik, kabuğun sırayla yürüttüğü komutları tutan metin dosyasıdır; `#` ile başlayan
  kelimeler yorum açar.
- Doğrudan çalıştırma çalıştırma izni ister; izin yoksa kod 126, ad `PATH` içinde
  bulunamazsa 127 döner.
- İlk iki baytı `#!` olan dosyada çekirdek, satırın kalanındaki saltık yolu yorumlayıcı
  olarak başlatır; arama yapmaz ve yalnızca ilk satıra bakar.
- `#!/bin/sh` yalnızca POSIX kabuk dilini güvence altına alır; bash eklentisi kullanan
  betik bash bildirmelidir.
- `bash betik` yeni süreç yaratır, `. betik` yaratmaz; ikincisinde atamalar çağıran
  kabukta kalır.

#### Sonraki Adım

Betiğin gövdesindeki kütük adı şimdilik sabit yazılı. Onu bir değişkene almak ilk
bakışta yalnızca düzen meselesi görünür; oysa kabukta değişken genişletmenin sonucu
tırnak kullanımına göre değişir ve tırnaksız bir genişletme, boşluk içeren dosya
adlarında betiği sessizce yanlış çalıştırır. Sonraki ders genişletme, kelime bölme ve
alıntılama kurallarını ele alır.

### Değişkenler ve Tırnak Kuralları

Source: https://academia.sh/tr/kurslar/kabuk-programlama/degiskenler-ve-tirnak-kurallari

Atama sözdizimi, genişletme sırası, kelime bölme ve yol adı genişletmesi, üç alıntılama biçimi ve öntanımlı değer gösterimleri.

Betikteki kütük adı şimdilik sabit yazılı. Onu bir değişkene almak ilk bakışta düzen
meselesi görünür. Kabukta ise bu, dilin en çok hata üreten alanına girmek demektir:
bir değişkenin genişletilmesi tek bir işlem değildir, birkaç aşamalı bir süreçtir ve
aşamaların hangisinin uygulanacağı tırnak kullanımına bağlıdır.

Programlama Temelleri kursunda değişken, bir değere bağlanan addı; okunduğunda o değeri
verirdi. Kabukta durum farklıdır: değişken okunduğunda ortaya çıkan metin, komut satırının
geri kalanıyla birlikte yeniden çözümlenir. Bu dersin konusu tam olarak o yeniden
çözümlemedir.

#### Atama

Atama, eşittir işaretinin iki yanında boşluk bırakılmadan yazılır.

```sh
# dene.sh
ad = deger
```

```
dene.sh: line 2: ad: command not found
```

Kabuk `ad` kelimesini komut adı, `=` ve `deger` kelimelerini argüman sandı. Kabuk
satırını önce boşluğa göre kelimelere ayırdığı için başka türlü davranamazdı. Doğru
yazımda boşluk yoktur:

```sh
ad=deger
echo "[$ad] [${ad}] [${ad}suz]"
```

```
[deger] [deger] [degersuz]
```

Süslü ayraçlı biçim, ad ile onu izleyen metnin sınırını belirtir. `$adsuz` yazılsaydı
kabuk `adsuz` adlı bir değişken arardı.

Kabukta değişkenlerin tipi yoktur; her değer bir karakter dizisidir. `sayi=5` ataması bir
tam sayı üretmez, iki karakterlik bir dizgi üretir. Sayısal işlem gerektiğinde ayrı bir
gösterim kullanılır; bu, Aritmetik ve Komut Yerine Koyma dersinin konusudur.

#### Genişletmeden Sonra Ne Olur

Bir değişken genişletildiğinde iş bitmez. Kabuk, tırnaksız genişletmenin sonucuna iki
işlem daha uygular:

1. **Kelime bölme (word splitting).** Sonuç metni, `IFS` değişkenindeki karakterlere
   göre kelimelere ayrılır. `IFS`'in öntanımlı değeri boşluk, sekme ve satır sonudur.
2. **Yol adı genişletmesi (pathname expansion).** Kelimelerde `*`, `?`, `[...]`
   karakterleri varsa eşleşen dosya adlarıyla değiştirilir.

İki işlemin sonucu gözlemlenebilir. Aşağıdaki yardımcı, aldığı argüman sayısını ve her
argümanı ayrı ayrı yazar:

```sh
say() { printf 'argüman sayisi: %d\n' "$#"; printf '  <%s>\n' "$@"; }
```

Her blok tek başına çalışabilsin diye tanım yinelenir:

```sh
say() { printf 'argüman sayisi: %d\n' "$#"; printf '  <%s>\n' "$@"; }
metin="iki   bosluklu"
say $metin
say "$metin"
```

```
argüman sayisi: 2
  <iki>
  <bosluklu>
argüman sayisi: 1
  <iki   bosluklu>
```

Tırnaksız genişletmede iki kelime, tırnaklı genişletmede tek kelime oluştu. Aradaki üç
boşluk da tırnaksız durumda kayboldu — kelime bölme ayırıcıları saklamaz.

#### Tırnaksız Genişletmenin Bedeli

Bu davranışın betiklerdeki karşılığı somuttur. Boşluk içeren bir dosya adı düşünün:

```sh
say() { printf 'argüman sayisi: %d\n' "$#"; printf '  <%s>\n' "$@"; }
kutuk="erisim kutugu.log"
say $kutuk
say "$kutuk"
```

```
argüman sayisi: 2
  <erisim>
  <kutugu.log>
argüman sayisi: 1
  <erisim kutugu.log>
```

Tırnaksız yazımda komut, var olmayan iki dosya adı aldı. `rm $kutuk` gibi bir satırda bu,
yanlış dosyanın silinmesi anlamına gelir.

Yol adı genişletmesi ikinci bir katman ekler. Değişkenin içinde kalıp karakteri varsa,
tırnaksız genişletme dosya sistemine bakar:

```sh
say() { printf 'argüman sayisi: %d\n' "$#"; printf '  <%s>\n' "$@"; }
cd "$(mktemp -d)"          # yalnızca iki dosyanın bulunduğu boş bir dizin
: > "erisim kutugu.log"
: > yok.log

kalip="*.log"
say $kalip
say "$kalip"
```

```
argüman sayisi: 2
  <erisim kutugu.log>
  <yok.log>
argüman sayisi: 1
  <*.log>
```

Sıraya dikkat edin: değişkenin değeri `*.log` idi; genişletme, kelime bölme ve yol adı
genişletmesinden sonra iki dosya adı ortaya çıktı. Üstelik `erisim kutugu.log` tek
argüman olarak geldi — çünkü **yol adı genişletmesinin sonucu kelime bölmeye
uğramaz.** Bölme yalnızca değişkenin kendi değerine uygulanır.

Buradan kursun geri kalanında geçerli olacak kural çıkar:

> **Değişken genişletmeleri her zaman çift tırnak içine alınır.** Tırnaksız bırakmak,
> kelime bölme ya da yol adı genişletmesi bilinçli olarak isteniyorsa yapılır.

Bu kural `"$var"`, `"$@"`, `"$(komut)"` ve `"${dizi[@]}"` için de geçerlidir.

#### Üç Alıntılama Biçimi

**Çift tırnak** genişletmeleri uygular ama kelime bölme ve yol adı genişletmesini
engeller. Değişken, komut ve aritmetik genişletme içeride çalışır.

**Tek tırnak** hiçbir şeyi genişletmez; içerideki her karakter harfi harfine geçer. Tek
tırnak içinde tek tırnak kullanılamaz — ters bölü bile onu kaçırmaz.

**Ters bölü**, kendisinden sonraki tek karakteri özel anlamından arındırır. Çift tırnak
içinde yalnızca `$`, `` ` ``, `"`, `\` ve satır sonu için etkilidir; diğer karakterlerin
önünde harfi harfine kalır.

```sh
metin="iki   bosluklu"
echo $metin
echo "$metin"
echo '$metin'
```

```
iki bosluklu
iki   bosluklu
$metin
```

İlk satırda `echo` iki ayrı argüman aldı ve aralarına tek boşluk koyarak yazdı; ikinci
satırda boşluklar korundu; üçüncüde genişletme hiç olmadı.

Düzenli ifadeler ve `sed` kalıpları tek tırnak içinde yazılır: kalıplarda geçen `$`, `*`
ve `\` karakterlerinin kabuk tarafından yorumlanması istenmez. Bu kural Metin İşleme
konusunun tamamında geçerli olacak.

#### IFS ve Bilinçli Bölme

`IFS` (internal field separator) değişkeni, kelime bölmenin hangi karakterlerde
yapılacağını belirler. Değiştirmek, ayrılmış bir satırı alanlarına bölmenin yollarından
biridir:

```sh
say() { printf 'argüman sayisi: %d\n' "$#"; printf '  <%s>\n' "$@"; }
satir="10.0.0.12:404:512"
IFS=:
say $satir
```

```
argüman sayisi: 3
  <10.0.0.12>
  <404>
  <512>
```

`IFS` genel bir ayardır; değiştirildikten sonra o kabuktaki tüm tırnaksız genişletmeleri
etkiler. Eski değerine döndürmeyi unutmak, betiğin ilerideki satırlarında açıklaması güç
hatalar üretir. Doğru desen, `IFS`'i yalnızca tek bir komut için ayarlamaktır:

```sh
IFS=: read -r adres kod bayt <<< "$satir"
```

Komuttan önce yazılan atama yalnızca o komutun ortamında geçerlidir; komut bitince eski
değer geri gelir. Bu desen Döngüler dersinde kütüğü satır satır okurken kullanılacak.

#### Öntanımlı Değer Gösterimleri

Kabuk, tanımsız veya boş değişkenleri yerinde ele almak için bir gösterim ailesi sunar.
İki nokta üst üste karakterinin varlığı, "boş" değerin de eksik sayılıp sayılmayacağını
belirler.

| Gösterim | Değişken tanımsızsa | Değişken boşsa |
|---|---|---|
| `${v-yedek}` | `yedek` | boş dizgi |
| `${v:-yedek}` | `yedek` | `yedek` |
| `${v=yedek}` | atar ve `yedek` verir | boş dizgi |
| `${v:=yedek}` | atar ve `yedek` verir | atar ve `yedek` verir |
| `${v:?ileti}` | hata verip çıkar | hata verip çıkar |
| `${v:+deger}` | boş dizgi | boş dizgi |
| `${#v}` | 0 | 0 |

```sh
unset KUTUK
echo "1 tanimsiz, :- ile: [${KUTUK:-erisim.log}]"
KUTUK=""
echo "2 bos, :- ile: [${KUTUK:-erisim.log}]"
echo "3 bos, -  ile: [${KUTUK-erisim.log}]"
```

```
1 tanimsiz, :- ile: [erisim.log]
2 bos, :- ile: [erisim.log]
3 bos, -  ile: []
```

Ayrım önemlidir: bir seçeneğin bilinçli olarak boş bırakılması ile hiç verilmemesi farklı
durumlardır. `:-` ikisini aynı sayar, `-` ayırır.

`:?` biçimi zorunlu değişkenleri bildirmenin en kısa yoludur:

```sh
# dene.sh
unset ZORUNLU
echo "[${ZORUNLU:?tanimlanmali}]"
```

```
dene.sh: line 3: ZORUNLU: tanimlanmali
```

Betik bu noktada sıfırdan farklı kodla durur. Hata Yönetimi dersinde bu gösterim katı
kiple birlikte kullanılacak.

#### Ortam Değişkenleri ve Kapsam

Kabuk değişkenleri öntanımlı olarak yalnızca o kabuğa aittir. `export`, değişkeni
**ortama (environment)** koyar; o noktadan sonra başlatılan alt süreçler onu görür.

```sh
export DIL=tr
bash -c 'echo "alt surecte DIL=$DIL"'
YEREL=x
bash -c 'echo "alt surecte YEREL=[${YEREL-yok}]"'
```

```
alt surecte DIL=tr
alt surecte YEREL=[yok]
```

Aktarım tek yönlüdür: alt süreç ortamı değiştirse bile üst süreç bundan etkilenmez.
Boru Hattı dersindeki kural burada da geçerlidir.

`readonly` bir değişkeni değiştirilemez yapar:

```sh
# dene.sh
readonly SABIT=5
SABIT=6
```

```
dene.sh: line 3: SABIT: readonly variable
```

Adlandırma sözleşmesi yaygındır ve uyulması yararlıdır: **ortama giren ve dış dünyayla
paylaşılan adlar büyük harfle, betiğe özgü adlar küçük harfle** yazılır. Küçük harf
kullanmak ayrıca `PATH`, `HOME`, `IFS` gibi kabuk değişkenlerinin kazara ezilmesini
önler.

#### Betiğe Uygulama

`rapor.sh` betiği, sabit yazılı kütük adından kurtulur:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.

kutuk="${KUTUK:-erisim.log}"

echo "== Kutuk: $kutuk =="
echo "== En cok istenen yollar =="
cut -d' ' -f7 "$kutuk" | sort | uniq -c | sort -rn | head -5
```

```
== Kutuk: erisim.log ==
== En cok istenen yollar ==
      6 /api/veri
      4 /yok.html
      4 /index.html
      3 /urun/45
      3 /urun/12
```

Üç karar birlikte alındı: ad bir değişkene çekildi, ortamdan gelen `KUTUK` değeri
öncelikli kılındı ve `"$kutuk"` genişletmesi tırnaklandı. Üçüncüsü olmadan, boşluk içeren
bir yol verildiğinde betik sessizce yanlış dosyayı okumaya çalışırdı.

#### Özet

- Atama, eşittir işaretinin iki yanında boşluk bırakmaz; kabuk satırı kelimelere ayırdığı
  için başka türlü ayrıştıramaz.
- Tırnaksız genişletmenin sonucuna kelime bölme ve yol adı genişletmesi uygulanır; yol
  adı genişletmesinin sonucu yeniden bölünmez.
- Değişken genişletmeleri her zaman çift tırnak içine alınır; tırnaksız bırakmak bilinçli
  bir karardır.
- Çift tırnak genişletir ve bölmeyi engeller, tek tırnak hiçbir şeyi genişletmez, ters
  bölü tek karakteri arındırır.
- `-` ile `:-` arasındaki fark, boş değerin eksik sayılıp sayılmamasıdır; `:?` zorunlu
  değişkeni bildirir.
- `export` değişkeni alt süreçlere aktarır; aktarım tek yönlüdür.

#### Sonraki Adım

Kütük adı artık ortam değişkeninden gelebiliyor, ama betiği çağıran kişi için doğal olan
yol bu değildir: `./rapor.sh erisim.log` yazmak beklenir. Sonraki ders betiğe komut
satırından değer geçirmeyi ele alır — konumsal parametreler, argüman listesinin
tırnaklanması ve `"$@"` ile `"$*"` arasındaki, boşluklu argümanlarda ortaya çıkan fark.

### Özel Değişkenler ve Argümanlar

Source: https://academia.sh/tr/kurslar/kabuk-programlama/ozel-degiskenler-ve-argumanlar

Konumsal parametreler, argüman listesinin iki gösterimi, listeyi tüketen döngü ve seçenek ayrıştırma.

Kütük adı artık bir değişkenden geliyor, ama betiği çağıran için doğal yol bu değildir.
`KUTUK=erisim.log ./rapor.sh` yazmak yerine `./rapor.sh erisim.log` yazmak beklenir.
Komut satırından gelen değerler, kabukta ayrı bir mekanizmayla — **konumsal parametreler
(positional parameters)** ile — karşılanır.

Programlama Temelleri kursunda fonksiyon parametreleri adlarıyla tanımlanıyordu. Kabukta
adlandırma yoktur: parametreler numaralanır ve numaralarıyla okunur. Bu ders o numaraları,
listeyi bir bütün olarak ele almanın iki yolunu ve seçenek ayrıştırmayı kurar.

#### Konumsal Parametreler

`$1`, `$2`, … betikçe alınan argümanları sırayla verir. `$0` betiğin çağrıldığı adı,
`$#` argüman sayısını tutar.

```sh
#!/usr/bin/env bash
# arg.sh
echo "betik adi     : $0"
echo "arguman sayisi: $#"
echo "ilk           : $1"
echo "ikinci        : $2"
echo "onuncu        : ${10}"
```

```sh
chmod +x arg.sh
./arg.sh a b c d e f g h i j
```

```
betik adi     : ./arg.sh
arguman sayisi: 10
ilk           : a
ikinci        : b
onuncu        : j
```

Dokuzdan büyük numaralar süslü ayraç ister. `$10` yazılsaydı kabuk bunu `$1` ve `0` diye
okur, ilk argümanın ardına sıfır ekleyerek `a0` üretirdi. Ayraç kullanımı bu yüzden
zorunludur.

`$0`, betiğin çalıştırılırken yazıldığı addır; dosyanın gerçek adı olmak zorunda değildir
ve saltık yol içermeyebilir. Kullanım iletilerinde `$0` yazmak yaygındır ama betiğin
adını sabit yazmak daha okunaklı bir çıktı verir.

#### Argüman Listesi: `"$@"` ve `"$*"`

Tüm argümanları birlikte aktarmak için iki gösterim vardır ve aralarındaki fark yalnızca
tırnak içinde ortaya çıkar.

- `"$@"`, her argümanı **ayrı bir kelime** olarak genişletir.
- `"$*"`, tüm argümanları `IFS`'in ilk karakteriyle birleştirip **tek kelime** üretir.
- Tırnaksız `$@` ve `$*`, ikisi de kelime bölmeye uğrar ve ayrım kaybolur.

```sh
#!/usr/bin/env bash
# liste.sh
say() { printf 'sayi=%d\n' "$#"; printf '  <%s>\n' "$@"; }
echo '--- "$@" ---'; say "$@"
echo '--- "$*" ---'; say "$*"
echo '--- $@   ---'; say $@
```

```sh
chmod +x liste.sh
./liste.sh "erisim kutugu.log" 404
```

```
--- "$@" ---
sayi=2
  <erisim kutugu.log>
  <404>
--- "$*" ---
sayi=1
  <erisim kutugu.log 404>
--- $@   ---
sayi=3
  <erisim>
  <kutugu.log>
  <404>
```

Üç sonuç üç farklı anlam taşır. İlki, çağıranın verdiği sınırları koruyarak listeyi
aktarır. İkincisi listeyi tek metne çevirir — ileti yazdırmak dışında bir işe yaramaz.
Üçüncüsü, boşluk içeren argümanı ikiye böler ve çağıranın niyetini bozar.

Kural: **argümanları başka bir komuta aktarırken her zaman `"$@"` kullanın.** Bu, önceki
dersteki tırnaklama kuralının konumsal parametrelere uzanmasıdır.

#### Listeyi Tüketmek

`shift` komutu konumsal parametreleri bir sola kaydırır: `$2` yeni `$1` olur, `$#` bir
azalır. Argümanları sırayla işleyen döngülerin temelidir.

```sh
#!/usr/bin/env bash
# shift.sh
while [ "$#" -gt 0 ]; do
  echo "islenen: $1 (kalan: $#)"
  shift
done
```

```sh
chmod +x shift.sh
./shift.sh bir iki uc
```

```
islenen: bir (kalan: 3)
islenen: iki (kalan: 2)
islenen: uc (kalan: 1)
```

`shift N` biçimi bir defada `N` parametre atar. Kalan parametre sayısından fazlasını
atmaya çalışmak sıfırdan farklı kod döndürür ve listeyi değiştirmez.

#### Betiğe İlişkin Özel Değişkenler

Kabuk, çalışan betik hakkında birkaç değeri özel adlarda tutar.

| Ad | İçerik |
|---|---|
| `$?` | son komutun çıkış kodu |
| `$$` | çalışan kabuğun süreç numarası |
| `$!` | arka planda başlatılan son sürecin numarası |
| `$-` | etkin kabuk seçenekleri |
| `$0` | betiğin çağrıldığı ad |
| `$#` | konumsal parametre sayısı |

`$$` geçici dosya adı üretmekte sık kullanılır; ancak süreç numaraları yeniden
kullanılabildiği için tek başına güvenli değildir. Tuzaklar ve Temizlik dersinde `mktemp`
ile doğru yol gösterilecek.

`$!` arka plan süreçlerini beklemek ve durdurmak için gerekir; Çıkış Kodları dersinde
sinyal kodları `$$` ile üretilmişti.

#### Seçenek Ayrıştırma

Argümanları elle ayrıştırmak — birinci argüman seçenekse, ikinci onun değeriyse … —
kısa sürede okunmaz hâle gelir. `getopts` yerleşik komutu kısa seçenekleri sözleşmeye
uygun biçimde ayrıştırır.

`getopts` iki argüman alır: tanınan seçeneklerin dizgisi ve bulunan seçeneğin yazılacağı
değişken adı. Dizgideki bir harfin ardından `:` gelirse o seçenek değer bekler; değer
`OPTARG` değişkenine konur. Dizginin başındaki `:`, hata iletilerini `getopts`'un değil
betiğin üreteceğini bildirir.

```sh
#!/usr/bin/env bash
# sec.sh
adet=5
ayrinti=0
while getopts ':n:va' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    v)  ayrinti=1 ;;
    a)  echo "kullanim: sec.sh [-n SAYI] [-v] KUTUK"; exit 0 ;;
    :)  echo "hata: -$OPTARG deger bekliyor" >&2; exit 2 ;;
    \?) echo "hata: bilinmeyen secenek -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))
echo "adet=$adet ayrinti=$ayrinti kalan=$# ilk=[${1-yok}]"
```

Dört çağrı, dört davranışı gösterir:

```sh
chmod +x sec.sh
./sec.sh -n 3 -v erisim.log
./sec.sh erisim.log
./sec.sh -n
./sec.sh -z
```

```
adet=3 ayrinti=1 kalan=1 ilk=[erisim.log]
adet=5 ayrinti=0 kalan=1 ilk=[erisim.log]
hata: -n deger bekliyor
hata: bilinmeyen secenek -z
```

Son iki çağrı `2` koduyla sonlandı. Kullanım hatasını başka bir hata sınıfından ayrı bir
kodla bildirmek, betiği zincirde kullanılabilir kılar.

`OPTIND`, `getopts`'un işlediği son argümanın sırasını tutar. Döngüden sonraki
`shift $((OPTIND - 1))` satırı seçenekleri listeden atar; geriye yalnızca seçenek olmayan
argümanlar kalır. Bu satır unutulursa `$1` hâlâ ilk seçeneği gösterir.

`getopts` yalnızca tek harfli seçenekleri tanır; `--ayrinti` gibi uzun seçenekler için
destek yoktur. Uzun seçenek gerekiyorsa `while`–`case` ile elle ayrıştırma yazılır. Ayrıca
`case` dallarındaki `\?` yazımındaki ters bölü gereklidir: `?` `case` kalıplarında tek
karakter eşleyen özel bir simgedir.

#### Argüman Sonu İşareti

Tire ile başlayan bir değer, seçenek sanılabilir. Örneğin `-eski.log` adlı bir dosya,
çoğu komut tarafından seçenek olarak yorumlanır. Sözleşme, argüman listesinde `--`
görüldüğünde ondan sonrasının seçenek olmadığını kabul etmektir. Aşağıdaki kalıp tireyle
başlar ve kütükteki her satırda geçen `- - [` dizisini arar; `--` olmasa `grep` kalıbı
seçenek sanardı:

```sh
grep -c -- '- - \[' erisim.log
```

```
30
```

Kendi betiklerinizde de `getopts` bu işareti tanır ve orada durur. Değişkenden gelen
dosya adlarını başka bir komuta aktarırken `--` yazmak, kullanıcı girdisiyle seçenek
karışmasını önler.

#### Betiğe Uygulama

`rapor.sh` artık bir arayüz kazanır: kaç satır listeleneceği `-n` ile, kütük adı ilk
konumsal parametreyle verilir.

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
# Kullanim: rapor.sh [-n SAYI] [KUTUK]

adet=5

while getopts ':n:' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    :)  echo "hata: -$OPTARG deger bekliyor" >&2; exit 2 ;;
    \?) echo "hata: bilinmeyen secenek -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

kutuk="${1:-erisim.log}"

echo "== Kutuk: $kutuk (ilk $adet yol) =="
cut -d' ' -f7 "$kutuk" | sort | uniq -c | sort -rn | head -"$adet"
```

```sh
chmod +x rapor.sh
./rapor.sh -n 3
```

```
== Kutuk: erisim.log (ilk 3 yol) ==
      6 /api/veri
      4 /yok.html
      4 /index.html
```

Betik hâlâ eksik: verilen kütük yoksa `cut` hata verir ve rapor başlığı boşuna basılmış
olur. Bu eksik sonraki dersin konusudur.

#### Özet

- Konumsal parametreler `$1`'den başlar; dokuzdan büyük numaralar süslü ayraç ister.
  `$#` sayıyı, `$0` çağrılan adı verir.
- `"$@"` argümanları ayrı kelimeler olarak, `"$*"` tek kelime olarak genişletir;
  tırnaksız yazımda ikisi de kelime bölmeye uğrar. Aktarımda `"$@"` kullanılır.
- `shift` listeyi bir sola kaydırır ve argümanları sırayla tüketen döngüyü mümkün kılar.
- `getopts` kısa seçenekleri ayrıştırır; `OPTARG` değeri, `OPTIND` işlenen sırayı tutar
  ve döngüden sonra `shift $((OPTIND - 1))` gerekir.
- `--` işareti, ondan sonrasının seçenek olmadığını bildirir ve tire ile başlayan
  değerleri korur.

#### Sonraki Adım

Betik, kendisine verilen kütüğün var olduğunu ve okunabildiğini varsayıyor. Bu varsayımı
sınamak için bir koşul gerekir; kabukta koşul, bir komutun çıkış kodudur. Sonraki ders
dosya, dizgi ve sayı testlerini, `[ ]` ile `[[ ]]` arasındaki farkı ve bu farkın neden
sözdizim tercihi değil doğruluk meselesi olduğunu ele alır.

### Koşullar ve Testler

Source: https://academia.sh/tr/kurslar/kabuk-programlama/kosullar-ve-testler

Koşulun bir çıkış kodu olması, dosya/dizgi/sayı testleri, tek ve çift ayraç arasındaki ayrıştırma farkı, kalıp eşleme ve çoklu seçim.

Betik, kendisine verilen kütüğün var olduğunu ve okunabildiğini varsayıyor. Varsayımı
sınamak bir koşul gerektirir. Programlama Temelleri kursunda koşul, mantıksal bir
ifadeydi ve doğru ya da yanlış değerine indirgenirdi.

Kabukta böyle bir tip yoktur. **Koşul, çalıştırılan bir komuttur; sonucu çıkış kodudur.**
`if` yapısı bir ifadeyi değerlendirmez, bir komut çalıştırır ve kodu 0 ise gövdeyi
yürütür. Bu tek fark, kabuk koşullarının tüm sözdizim tuhaflıklarını açıklar.

#### `if` Bir Komut Alır

Genel biçim şöyledir; `KOMUT` yerine herhangi bir komut gelir ve çıkış kodu 0 ise
ilgili gövde yürütülür:

```text
if KOMUT; then
  ...
elif KOMUT; then
  ...
else
  ...
fi
```

Somut bir örnek:

```sh
if grep -q ' 404 ' erisim.log; then
  echo "404 var"
elif grep -q ' 500 ' erisim.log; then
  echo "404 yok, 500 var"
else
  echo "ikisi de yok"
fi
```

```
404 var
```

Herhangi bir komut koşul olabilir:

```sh
if grep -q '404' erisim.log; then
  echo "kutukte 404 var"
fi
```

Koşullar için kullanılan `[ ... ]` yazımı bir sözdizim öğesi değil, `test` adlı komutun
ikinci adıdır. Bu yüzden `[` ile ilk argüman arasında boşluk zorunludur ve kapanış `]`
bu komutun son argümanıdır. `[-f dosya]` yazımı `[-f` adında bir komut arar ve
bulunamaz.

#### Dosya Testleri

`test`, dosya sistemine ilişkin bir işleçler ailesi tanır.

| İşleç | Doğru olduğu durum |
|---|---|
| `-e` | yol var (türü ne olursa olsun) |
| `-f` | düz dosya |
| `-d` | dizin |
| `-r`, `-w`, `-x` | okuma / yazma / çalıştırma izni var |
| `-s` | dosya var ve boyutu sıfırdan büyük |
| `-L` | sembolik bağ |
| `a -nt b` | `a`, `b`'den daha yeni değiştirilmiş |

```sh
[ -f erisim.log ]; echo "duz dosya  -> $?"
[ -d erisim.log ]; echo "dizin      -> $?"
[ -x erisim.log ]; echo "calistirma -> $?"
```

```
duz dosya  -> 0
dizin      -> 1
calistirma -> 1
```

İzin testleri, dosyanın izin bitlerini değil **çağıran sürecin o dosyada o işlemi yapıp
yapamayacağını** sınar; süper kullanıcı için sonuç farklı çıkabilir. Linux'a Giriş
kursunda kurulan izin modeli burada davranışa dönüşür.

`-e` ile `-f` ayrımı ihmal edilmemelidir: bir dizin de `-e` sınamasını geçer, ama `cut`
ile okunamaz.

#### Dizgi ve Sayı Testleri

Dizgi testleri: `-z` boş mu, `-n` boş değil mi, `=` eşit mi, `!=` farklı mı.

Sayı testleri ayrı işleçler kullanır: `-eq`, `-ne`, `-lt`, `-le`, `-gt`, `-ge`.

İki ailenin ayrı olması gereklidir, çünkü kabukta her değer bir karakter dizisidir ve
"eşitlik" iki farklı anlama gelir:

```sh
[ "007" =   "7" ]; echo "dizgi esitligi -> $?"
[ "007" -eq "7" ]; echo "sayi esitligi  -> $?"
```

```
dizgi esitligi -> 1
sayi esitligi  -> 0
```

`007` ile `7` farklı dizgilerdir ama aynı sayıdır. Hangi anlamın istendiğine yazan karar
verir; işleç seçimi bu kararın bildirimidir.

Sayı işleçleri yalnızca tam sayılarla çalışır. Ondalıklı değer verilirse `test` hata
bildirir. Ondalıklı karşılaştırma gerekiyorsa bir hesaplama aracına başvurulur; bu,
Aritmetik ve Komut Yerine Koyma dersinde ele alınacak.

#### Tek Ayracın Ayrıştırma Sorunu

`[` sıradan bir komut olduğu için argümanları önce kabuk tarafından genişletilir ve
kelimelere bölünür. Bölme sonucu, `test`'in beklediği argüman sayısını değiştirebilir.

```sh
# dene.sh
bos=""
[ $bos = "x" ]
```

```
dene.sh: line 3: [: =: unary operator expected
```

Genişletme sonrası `[` komutuna yalnızca `=`, `x`, `]` argümanları ulaştı; `test` bunu
tek işlenenli bir ifade sandı. Aynı sorun boşluk içeren değerde ters yönde çıkar:

```sh
# dene.sh
d="iki kelime"
[ $d = "iki kelime" ]
```

```
dene.sh: line 3: [: too many arguments
```

Her iki hata da tırnaklamayla giderilir:

```sh
[ "$bos" = "x" ]; echo "kod=$?"
```

```
kod=1
```

Eski betiklerde `[ "x$a" = "xb" ]` biçiminde bir kalıp görülür. Amacı, değişkenin
değerinin bir işleçle karışmasını önlemektir; tırnaklama yeterli olduğu için bu kalıp
artık gereksizdir, ama karşılaşıldığında ne yaptığı bilinmelidir.

#### Çift Ayraç

Bash ve ksh, `[[ ... ]]` adında ayrı bir sözdizim öğesi tanır. Bu bir komut değildir;
kabuk onu ayrıştırma aşamasında tanır ve içindeki genişletmelere **kelime bölme ile yol
adı genişletmesi uygulamaz.**

```sh
bos=""; d="iki kelime"
[[ $bos = "x" ]];        echo "bos     -> $?"
[[ $d = "iki kelime" ]]; echo "bosluklu -> $?"
```

```
bos     -> 1
bosluklu -> 0
```

Tırnaksız yazımlar hata vermedi. Bu, `[[ ]]` içinde tırnak gerekmediği anlamına gelir —
ama tırnak yazmak da zarar vermez ve alışkanlığı bozmamak için yeğlenebilir.

Çift ayracın üç ek yeteneği vardır.

**Kalıp eşleme.** `=` veya `==` işlecinin sağ yanı tırnaksızsa yol adı kalıbı olarak
yorumlanır:

```sh
yol="/api/veri"
[[ $yol == /api/* ]]   && echo "api altinda"
[[ $yol == "/api/*" ]] || echo "tirnaklanmis kalip harfi harfine"
```

```
api altinda
tirnaklanmis kalip harfi harfine
```

Sağ yanın tırnaklanması kalıbı sıradan dizgiye çevirir. Bu, kalıp eşlemesinin istenmediği
durumlarda gerekli olan denetimdir.

**Düzenli ifade eşleme.** `=~` işleci sağ yanı genişletilmiş düzenli ifade sayar:

```sh
kod="404"
[[ $kod =~ ^[45][0-9][0-9]$ ]] && echo "hata sinifi kodu"
```

```
hata sinifi kodu
```

Düzenli ifade yazımı Metin İşleme konusunda ayrıntılı ele alınacak. Burada önemli olan,
kalıbın tırnaksız yazılmasıdır: tırnaklanan bir sağ yan harfi harfine eşleşme arar.

**Mantıksal işleçler.** `&&` ve `||`, ayraç içinde koşulları birleştirir:

```sh
[[ -f erisim.log && -r erisim.log ]] && echo "okunabilir dosya"
```

```
okunabilir dosya
```

`[ ]` içinde aynı iş için `-a` ve `-o` işleçleri bulunur, ancak bunlar ayrıştırma
belirsizliği ürettikleri için önerilmez. Taşınabilir yazımda iki ayrı test `&&` ile
zincirlenir:

```sh
[ -f erisim.log ] && [ -r erisim.log ] && echo "okunabilir dosya"
```

**`[[ ]]` bash eklentisidir; POSIX kabuk dilinde yoktur.** `#!/bin/sh` bildiren bir
betikte kullanılamaz. Karar kuralı: bash bildiriliyorsa `[[ ]]` yeğlenir — kelime bölme ve
yol adı genişletmesinden doğan ayrıştırma hatalarına karşı bağışıktır, ama kalıp/dizgi
ayrımı için sağ yanın tırnaklanması hâlâ gerekir; POSIX gerekiyorsa `[ ]` ve titiz
tırnaklama kullanılır.

#### Çoklu Seçim

Aynı değeri birçok olasılığa karşı sınayan `if`–`elif` zincirleri yerine `case` yazılır.
Kalıplar yol adı kalıbı sözdizimini kullanır.

```sh
for k in 200 301 404 500 xyz; do
  case "$k" in
    2??) sinif="basarili" ;;
    3??) sinif="yonlendirme" ;;
    4??) sinif="istemci hatasi" ;;
    5??) sinif="sunucu hatasi" ;;
    *)   sinif="tanimsiz" ;;
  esac
  printf '%-4s -> %s\n' "$k" "$sinif"
done
```

```
200  -> basarili
301  -> yonlendirme
404  -> istemci hatasi
500  -> sunucu hatasi
xyz  -> tanimsiz
```

`case`, ilk eşleşen dalı çalıştırır ve durur; dallar arasında düşme yoktur. Bir kalıba
birden çok seçenek `|` ile eklenir: `200|201|204)`. Son dal olarak yazılan `*)`, hiçbir
kalıba uymayan değerleri yakalar ve her `case` yapısında bulunmalıdır — eksikse tanınmayan
değer sessizce yok sayılır.

`case` POSIX'tir ve `[[ ]]` gerektirmeden kalıp eşleme sağlar; taşınabilir betiklerde
kalıp sınamasının doğru aracıdır.

#### Betiğe Uygulama

`rapor.sh` girdilerini sınayacak biçimde genişletilir. Hata sınıflarına ayrı kodlar
verilmesi, önceki dersteki sözleşmenin sürdürülmesidir.

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
# Kullanim: rapor.sh [-n SAYI] [KUTUK]

adet=5

while getopts ':n:' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    :)  echo "hata: -$OPTARG deger bekliyor" >&2; exit 2 ;;
    \?) echo "hata: bilinmeyen secenek -$OPTARG" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

kutuk="${1:-erisim.log}"

if ! [[ "$adet" =~ ^[0-9]+$ ]] || [ "$adet" -lt 1 ]; then
  echo "hata: -n icin pozitif tam sayi bekleniyor: $adet" >&2
  exit 2
fi

if [ ! -e "$kutuk" ]; then
  echo "hata: kutuk yok: $kutuk" >&2
  exit 3
elif [ ! -f "$kutuk" ]; then
  echo "hata: duz dosya degil: $kutuk" >&2
  exit 3
elif [ ! -r "$kutuk" ]; then
  echo "hata: okuma izni yok: $kutuk" >&2
  exit 3
elif [ ! -s "$kutuk" ]; then
  echo "uyari: kutuk bos: $kutuk" >&2
fi

echo "== Kutuk: $kutuk (ilk $adet yol) =="
cut -d' ' -f7 "$kutuk" | sort | uniq -c | sort -rn | head -"$adet"
```

Sayı sınamasında `[ "$adet" -lt 1 ]` testinden önce düzenli ifade sınaması yapılmasının
nedeni sıradır: `adet` sayı değilse `-lt` hata verirdi. `||` kısa devre yaptığı için ikinci
sınamaya yalnızca birincisi geçtiğinde ulaşılır.

Üç çağrı üç yolu izler:

```sh
chmod +x rapor.sh
./rapor.sh yok.log 2>&1;  echo "kod=$?"
./rapor.sh -n abc 2>&1;   echo "kod=$?"
./rapor.sh /etc 2>&1;     echo "kod=$?"
```

```
hata: kutuk yok: yok.log
kod=3
hata: -n icin pozitif tam sayi bekleniyor: abc
kod=2
hata: duz dosya degil: /etc
kod=3
```

#### Özet

- Kabukta koşul bir komuttur; `if` çıkış koduna bakar. `[ ]`, `test` komutunun ikinci
  adıdır ve çevresindeki boşluklar zorunludur.
- Dizgi eşitliği `=`, sayı eşitliği `-eq` ile sınanır; `007` ile `7` dizgi olarak farklı,
  sayı olarak aynıdır.
- `[ ]` içindeki genişletmeler kelime bölmeye uğradığı için tırnaklama zorunludur; boş
  veya boşluklu değer aksi hâlde ayrıştırma hatası üretir.
- `[[ ]]` ayrıştırma düzeyinde tanınır, bölme uygulamaz, kalıp ve düzenli ifade eşlemesi
  ile mantıksal işleçler sunar; bash eklentisidir.
- `case` ilk eşleşen dalı çalıştırır, düşme yoktur ve `*)` dalı her zaman yazılmalıdır.

#### Sonraki Adım

Rapor şu an tek bir listeyi üretiyor. Durum kodu sınıflarının her biri için ayrı sayım
istendiğinde aynı boru hattını elle çoğaltmak gerekir. Sonraki ders yinelemeyi ele alır:
liste üzerinde dönen döngüler, sayaçlı döngüler ve bir dosyayı satır satır okumanın —
kabukta hiç de apaçık olmayan — doğru biçimi.

### Döngüler

Source: https://academia.sh/tr/kurslar/kabuk-programlama/donguler

Liste, sayaç ve koşul tabanlı yineleme, dosya adları üzerinde güvenli dönme, satır satır okumanın doğru biçimi ve döngü denetimi.

Rapor şu an tek bir liste üretiyor. Durum kodu sınıflarının her biri için ayrı sayım
istendiğinde aynı boru hattını dört kez elle yazmak gerekir. Programlama Temelleri
kursunda bu, yinelemenin klasik gerekçesiydi.

Kabuktaki döngüler aynı yapıları kurar, ama listenin nereden geldiği kabuğun genişletme
kurallarına bağlıdır. Bu ders döngü biçimlerini ve — kabuk programlamanın en sık yanlış
yazılan parçası olan — dosyayı satır satır okuma desenini ele alır.

#### Liste Üzerinde Dönme

`for` döngüsü, kendisine verilen kelime listesinin her elemanı için gövdeyi çalıştırır.

```sh
for sinif in 2 3 4 5; do
  adet=$(cut -d' ' -f9 erisim.log | grep -c "^${sinif}")
  echo "${sinif}xx -> $adet"
done
```

```
2xx -> 18
3xx -> 3
4xx -> 6
5xx -> 3
```

`$( ... )` gösterimi bir komutun çıktısını değere çevirir; ayrıntısı bu konunun son
dersinde ele alınacak.

Listedeki "kelime" kavramı, önceki derslerde kurulan kelime bölme kavramıyla aynıdır.
Liste bir değişkenden geliyorsa, tırnak kullanımı doğrudan döngünün tur sayısını belirler:

```sh
kodlar="200 404 500"
for k in $kodlar;   do printf '<%s> ' "$k"; done; echo
for k in "$kodlar"; do printf '<%s> ' "$k"; done; echo
```

```
<200> <404> <500> 
<200 404 500> 
```

Bu, kelime bölmenin bilinçli olarak istendiği ender durumlardan biridir. Yine de değeri
tek bir dizgide tutup bölünmesine güvenmek kırılgandır; birden çok değer taşınacaksa dizi
kullanmak doğru araçtır. Diziler bu konunun yedinci dersinin konusudur.

#### Dosya Adları Üzerinde Dönme

Bir dizindeki dosyalar üzerinde dönmenin doğru yolu, yol adı kalıbını doğrudan listeye
yazmaktır:

```sh
mkdir -p kutukler; : > 'kutukler/a.log'; : > 'kutukler/b c.log'
for f in kutukler/*.log; do echo "  [$f]"; done
```

```
  [kutukler/a.log]
  [kutukler/b c.log]
```

Boşluk içeren ad tek parça kaldı; çünkü yol adı genişletmesinin sonucu kelime bölmeye
uğramaz. Bu, ikinci derste kurulan kuralın pratik karşılığıdır.

Aynı işi `for f in $(ls kutukler)` biçiminde yazmak yanlıştır: `ls` çıktısı bir metindir
ve kelime bölmeye uğrar; boşluk içeren her ad parçalanır. Kabukta dosya listesi üretmenin
aracı `ls` değil, yol adı genişletmesidir.

Kalıbın hiçbir dosyayla eşleşmemesi ayrı bir tuzaktır:

```sh
for f in kutukler/*.yok; do echo "  [$f]"; done
```

```
  [kutukler/*.yok]
```

Eşleşme yoksa kabuk kalıbı harfi harfine bırakır ve döngü bir kez, var olmayan bir dosya
adıyla döner. İki çözüm vardır. Taşınabilir olanı, gövdenin başına varlık sınaması
koymaktır:

```sh
for f in kutukler/*.yok; do
  [ -e "$f" ] || continue
  echo "  [$f]"
done
```

Bash'e özgü olanı `nullglob` seçeneğidir; eşleşmeyen kalıp boş listeye genişler ve döngü
hiç dönmez:

```sh
shopt -s nullglob
for f in kutukler/*.yok; do echo "  [$f]"; done
shopt -u nullglob
```

Seçenek genel bir ayardır ve etkin olduğu sürece tüm kalıpları etkiler; betiğin başında
açılıp bırakılması, ilerideki satırlarda beklenmedik davranışa yol açabilir.

#### Sayaçlı Döngüler

Sabit sayıda tur için üç yazım vardır.

**Ayraç genişletmesi** derleme zamanında sabit bir liste üretir:

```sh
for i in {1..5}; do printf '%s ' "$i"; done; echo
```

```
1 2 3 4 5 
```

Ayraç genişletmesi değişken içeremez — `{1..$n}` beklendiği gibi çalışmaz, çünkü ayraç
genişletmesi değişken genişletmesinden **önce** uygulanır. Bu, genişletme sırasının
gözlemlenebilir sonuçlarından biridir.

**C biçimli döngü** bash eklentisidir ve değişken sınır alabilir:

```sh
for ((i = 1; i <= 5; i++)); do printf '%s ' "$i"; done; echo
```

```
1 2 3 4 5 
```

**Dizi üreten komut**, taşınabilir üçüncü yoldur: `for i in $(seq 1 "$n")`. Komut
çağırması nedeniyle en pahalı seçenektir, ama POSIX kabuk dilinde çalışır.

#### Koşul Tabanlı Döngüler

`while`, koşul komutu 0 döndürdüğü sürece; `until` sıfırdan farklı döndürdüğü sürece
gövdeyi yineler.

```sh
sayac=0
until [ "$sayac" -ge 3 ]; do
  echo "sayac=$sayac"
  sayac=$((sayac + 1))
done
```

```
sayac=0
sayac=1
sayac=2
```

İkisi birbirinin yerine kullanılabilir; `until`, koşulun değillemesini yazmaktan kurtarır
ve okunurluğu artırdığı durumlarda yeğlenir.

#### Dosyayı Satır Satır Okumak

Kabuk programlamada en sık yanlış yazılan desen budur. Doğru biçim üç parçadan oluşur ve
her parçanın ayrı bir gerekçesi vardır:

```sh
sayac=0
while IFS= read -r satir; do
  sayac=$((sayac + 1))
done < erisim.log
echo "okunan satir: $sayac"
```

```
okunan satir: 30
```

**`-r` seçeneği** ters bölü işlemesini kapatır. Bu seçenek olmadan `read`, ters bölüyü bir
kaçış karakteri sayar ve veriyi bozar.

**`IFS=` ataması** satırın başındaki ve sonundaki boşlukların kırpılmasını engeller.

**Yönlendirme, boru değil.** Boru hattının sağ ucundaki döngü alt kabukta çalışır ve
atadığı değişkenler kaybolur; bu, Boru Hattı dersinde gösterilmişti.

Üç parçanın etkisi ölçülebilir. Aşağıdaki dosya üç satırdır: ilkinde iki boşluk, ikincisi
ters bölüyle başlar, üçüncüsünün iki ucunda boşluk vardır.

```sh
printf 'bir  iki\n\\ters bolulu\n  bosluklu kenar  \n' > garip.txt
while read satir; do printf '<%s>\n' "$satir"; done < garip.txt
```

```
<bir  iki>
<ters bolulu>
<bosluklu kenar>
```

```sh
while IFS= read -r satir; do printf '<%s>\n' "$satir"; done < garip.txt
```

```
<bir  iki>
<\ters bolulu>
<  bosluklu kenar  >
```

İlk yazımda ters bölü yutuldu ve kenar boşlukları kırpıldı. İkincisi dosyayı olduğu gibi
verdi.

Dördüncü bir ayrıntı, son satırda satır sonu bulunmamasıdır. `read` satır sonu görmeden
dosya sonuna ulaşırsa okuduğunu değişkene yazar ama sıfırdan farklı kod döndürür; döngü
koşulu bu kodu yanlış anlar ve son satır işlenmez:

```sh
printf 'birinci\nikinci' > eksik.txt
while IFS= read -r s; do echo "okundu: $s"; done < eksik.txt
```

```
okundu: birinci
```

Koşula bir ek yapılır: değişken boş değilse döngü bir tur daha döner.

```sh
while IFS= read -r s || [ -n "$s" ]; do echo "okundu: $s"; done < eksik.txt
```

```
okundu: birinci
okundu: ikinci
```

Bu kalıp, dış kaynaklı verinin okunduğu her betikte gereklidir; satır sonuyla bitmeyen
dosyalar nadir değildir.

#### Satırı Alanlara Bölerek Okumak

`read` birden çok değişken alırsa satırı `IFS`'e göre böler ve son değişkene kalan her
şeyi verir. Kütüğün alanları boşlukla ayrıldığı için doğrudan okunabilir:

```sh
head -3 erisim.log | while read -r adres _ _ _ _ yontem yol _ kod bayt; do
  printf '%-12s %-4s %-18s %s %s\n' "$adres" "${yontem#\"}" "$yol" "$kod" "$bayt"
done
```

```
10.0.0.12    GET  /index.html        200 5120
10.0.0.31    GET  /statik/stil.css   200 2048
10.0.0.12    GET  /urun/12           200 8410
```

`_` adı bir sözleşmedir, dil öğesi değil: ilgilenilmeyen alanlar aynı değişkene atanır.
`${yontem#\"}` gösterimi baştaki tırnağı siler; dizgi işleme gösterimleri Aritmetik ve
Komut Yerine Koyma dersinde toparlanacak.

Bu döngü, alan sayısı sabit olan veriler için yeterlidir. Alan sayısı değişken olduğunda
ya da hesaplama gerektiğinde alan tabanlı bir araç daha uygundur; Metin İşleme konusunda
aynı iş tek satıra inecek.

#### Döngü Denetimi

`break` içinde bulunduğu döngüyü sonlandırır, `continue` turu atlar. İkisi de sayı
argümanı alarak dış döngülere etki edebilir.

```sh
for k in 200 301 404 500 xyz 200; do
  case "$k" in
    [0-9][0-9][0-9]) : ;;
    *) echo "gecersiz kod, atlaniyor: $k"; continue ;;
  esac
  if [ "$k" -ge 500 ]; then echo "sunucu hatasi bulundu: $k"; break; fi
  echo "islendi: $k"
done
```

```
islendi: 200
islendi: 301
islendi: 404
sunucu hatasi bulundu: 500
```

`break 2` iki düzey dışarı çıkar:

```sh
for a in 1 2; do
  for b in x y; do
    [ "$b" = y ] && break 2
    echo "$a$b"
  done
done
```

```
1x
```

Sayılı `break` okunurluğu hızla bozar; iki düzeyden fazlasında döngüyü bir fonksiyona
alıp `return` kullanmak daha anlaşılır olur. Fonksiyonlar sonraki dersin konusudur.

#### Betiğe Uygulama

`rapor.sh` ikinci bir bölüm kazanır: durum kodu sınıflarının dağılımı.

```sh
kutuk=erisim.log

echo "== Durum kodu siniflari =="
for sinif in 2 3 4 5; do
  adet=$(cut -d' ' -f9 "$kutuk" | grep -c "^${sinif}")
  case "$sinif" in
    2) ad="basarili"       ;;
    3) ad="yonlendirme"    ;;
    4) ad="istemci hatasi" ;;
    5) ad="sunucu hatasi"  ;;
  esac
  printf '  %sxx %-16s %3d\n' "$sinif" "$ad" "$adet"
done
```

```
== Durum kodu siniflari ==
  2xx basarili          18
  3xx yonlendirme        3
  4xx istemci hatasi     6
  5xx sunucu hatasi      3
```

Sayıların toplamı otuzdur; kütükteki satır sayısına eşittir. Bu tür bir iç tutarlılık
sınaması, rapor betiklerinin doğruluğunu ucuz biçimde denetlemenin yoludur.

#### Özet

- `for` listesi, kabuğun genişletme kurallarıyla oluşur; tırnaksız değişken bölünür,
  tırnaklı bölünmez.
- Dosya listesi `ls` çıktısından değil yol adı genişletmesinden üretilir; eşleşme yoksa
  kalıp harfi harfine kalır ve `[ -e "$f" ] || continue` ya da `nullglob` gerekir.
- Ayraç genişletmesi değişken alamaz, çünkü değişken genişletmesinden önce uygulanır.
- Satır satır okumanın doğru biçimi `while IFS= read -r satir; do ... done < dosya`
  yazımıdır; boru kullanılırsa döngü alt kabuğa düşer.
- Satır sonuyla bitmeyen dosyalarda son satır ancak `|| [ -n "$satir" ]` ekiyle işlenir.

#### Sonraki Adım

Betik büyüdükçe aynı iş — hata bildirme, bölüm başlığı yazma, bir alanı sayma — birden
çok yerde tekrar ediyor. Sonraki ders fonksiyonları ele alır: kabukta fonksiyon tanımının
ne olduğunu, argümanların nasıl geçtiğini, `return` ile `exit` arasındaki farkı ve
kabuğun kapsam modelinin Programlama Temelleri kursundakinden hangi noktada ayrıldığını.

### Fonksiyonlar ve Kapsam

Source: https://academia.sh/tr/kurslar/kabuk-programlama/fonksiyonlar-ve-kapsam

Fonksiyon tanımı, argüman geçişi, çıkış koduyla değer döndürmenin ayrımı, yerel değişkenler ve dinamik kapsam.

Betik büyüdükçe aynı iş birden çok yerde tekrar ediyor: hata bildirme, bölüm başlığı
yazma, bir alanı sayıp sıralama. Programlama Temelleri kursunda bunun karşılığı
fonksiyondu ve fonksiyon, adlandırılmış parametreler alıp bir değer döndüren yapıydı.

Kabuktaki fonksiyon farklı bir şeydir. **Adlandırılmış bir komuttur**: konumsal
parametrelerle çağrılır, çıkış kodu döndürür ve sonucunu — bir değer üretiyorsa —
standart çıktısına yazar. Yani kabuk fonksiyonu, bir programın kabuk içindeki
karşılığıdır.

#### Tanım ve Çağrı

```sh
bolum() { printf '\n== %s ==\n' "$*"; }
bolum "En cok istenen yollar"
```

```

== En cok istenen yollar ==
```

Gövde süslü ayraçlar arasındadır. Ayraçlar sözcük olarak tanındığı için çevrelerinde
boşluk zorunludur ve son komuttan sonra `;` ya da satır sonu gerekir. Bash
`function ad { ... }` yazımını da tanır; POSIX olan biçim yukarıdakidir.

Fonksiyon çağrıldığı yerde tanımlı olmalıdır. Kabuk dosyayı yukarıdan aşağı okur;
tanımdan önceki bir çağrı "komut bulunamadı" hatası verir:

```sh
# dene.sh
cagir_once
echo "kod=$?"
cagir_once() { echo "gec kaldin"; }
```

```
dene.sh: line 2: cagir_once: command not found
kod=127
```

Bu yüzden fonksiyonlar betiğin başında, iş yapan kodun üstünde toplanır.

#### Argümanlar

Fonksiyon çağrıldığında konumsal parametreler fonksiyonunkilerle değiştirilir: `$1`,
`$2`, `$#`, `"$@"` fonksiyona verilen argümanları gösterir. Bir istisna vardır — `$0`
değişmez, betiğin adını göstermeye devam eder.

```sh
#!/usr/bin/env bash
# f.sh
kim() { echo "0=$0 1=$1 sayi=$#"; }
kim a b
```

```sh
chmod +x f.sh
./f.sh
```

```
0=./f.sh 1=a sayi=2
```

Argüman aktarımında üçüncü dersteki kural geçerlidir: `"$@"` kullanılır, `$*` yalnızca
ileti metni üretmek için yazılır.

Kabuk fonksiyonlarının imzası yoktur; eksik argümanla çağrılan fonksiyon hata vermez,
`$1` boş kalır. Zorunlu argümanlar gövdenin başında sınanmalıdır:

```sh
sinif_adi() {
  [ "$#" -eq 1 ] || { echo "sinif_adi: bir argüman bekler" >&2; return 2; }
  ...
}
```

#### Çıkış Kodu Döndürmek

`return` fonksiyonu sonlandırır ve çıkış kodunu belirler. Kod, süreç çıkış kodlarıyla
aynı sözleşmeye tabidir: 0 başarı, sıfırdan farklı başarısızlık, aralık 0–255.

```sh
gecerli_kod() {
  case "$1" in
    [1-5][0-9][0-9]) return 0 ;;
    *)               return 1 ;;
  esac
}
gecerli_kod 404; echo "404 -> $?"
gecerli_kod abc; echo "abc -> $?"
```

```
404 -> 0
abc -> 1
```

Aralık dışı değerler 256'ya göre indirgenir; `return 300` yazan bir fonksiyon `44`
döndürür.

`return` ile `exit` karıştırılmamalıdır. `return` yalnızca fonksiyondan çıkar; `exit`
betiğin tamamını sonlandırır.

```sh
#!/usr/bin/env bash
# cikis.sh
f() { echo "f icinde"; return 1; }
g() { echo "g icinde"; exit 4; }
f; echo "f sonrasi devam, kod=$?"
g; echo "BU SATIR YAZILMAZ"
```

```
f icinde
f sonrasi devam, kod=1
g icinde
```

Betiğin çıkış kodu, çıktısı atılarak okunur:

```sh
bash cikis.sh > /dev/null; echo $?
```

```
4
```

Kural: kitaplık niteliğindeki fonksiyonlar `return` kullanır ve kararı çağırana bırakır;
yalnızca "ölümcül hata" işlevi gören yardımcılar `exit` çağırır.

#### Değer Döndürmek

`return` bir değer döndürme aracı değildir; 0–255 aralığındaki bir kod, dizgi ya da büyük
sayı taşıyamaz. Kabukta değer döndürmenin yolu standart çıktıya yazmak, çağıranın da onu
komut yerine koymayla almasıdır.

```sh
sinif_adi() {
  case "$1" in
    2) echo "basarili"       ;;
    3) echo "yonlendirme"    ;;
    4) echo "istemci hatasi" ;;
    5) echo "sunucu hatasi"  ;;
    *) echo "tanimsiz"; return 1 ;;
  esac
}
ad=$(sinif_adi 4); echo "4 -> $ad"
```

```
4 -> istemci hatasi
```

Bu sözleşmenin doğrudan bir sonucu vardır: **değer döndüren bir fonksiyon, standart
çıktıya başka hiçbir şey yazamaz.** İlerleme iletisi ya da uyarı yazması gerekiyorsa
standart hataya yazar. Bu, konunun ilk dersindeki akış ayrımının pratikte neden gerekli
olduğunun en açık örneğidir.

İkinci sonuç maliyettir: komut yerine koyma bir alt kabuk yaratır. Döngü içinde çok sayıda
çağrı yapan betiklerde bu, ölçülebilir bir yavaşlamaya yol açar. Değeri bir genel
değişkene yazmak alternatiftir; okunurluğu düşürdüğü için ancak gerektiğinde başvurulur.

#### Kapsam: Öntanımlı Olarak Genel

Kabukta değişkenler öntanımlı olarak **geneldir**. Fonksiyon içinde yapılan atama, dışarıda
da görünür.

```sh
sayac=0
artir() { sayac=$((sayac + 1)); }
artir; artir; echo "sayac=$sayac"
```

```
sayac=2
```

Bu, Programlama Temelleri kursundaki varsayılanın tersidir; orada fonksiyon gövdesindeki
atama yerel bir ad yaratırdı. Kabuğun davranışı, fonksiyonun bir "komut" olarak
tasarlanmasının sonucudur — komutlar da kabuk değişkenlerini görür.

`local` bildirimi adı fonksiyona bağlar:

```sh
x="disarida"
denemel() { local x="iceride"; echo "fonksiyon icinde: $x"; }
denemel; echo "fonksiyon disinda: $x"
```

```
fonksiyon icinde: iceride
fonksiyon disinda: disarida
```

`local` POSIX kabuk dilinde tanımlı değildir; yaygın kabukların hemen hepsinde bulunur ama
`#!/bin/sh` bildiren bir betikte kullanılması taşınabilirlik güvencesini kaldırır.

Kural: **fonksiyon içinde kullanılan her geçici değişken `local` bildirilir.** Bildirilmeyen
bir ad, çağıran bağlamdaki aynı adlı değişkeni sessizce ezer; bu, uzun betiklerde en zor
bulunan hata sınıfıdır.

#### Dinamik Kapsam

`local` bildirilen bir ad, yalnızca tanımlandığı fonksiyona değil, **o fonksiyonun
çağırdığı tüm fonksiyonlara** görünür. Kabuğun kapsam modeli sözcüksel değil
**dinamiktir**.

```sh
ic()  { echo "ic gorur: $gizli"; }
dis() { local gizli="dis fonksiyondan"; ic; }
dis
echo "ana kabuk gorur: [${gizli-tanimsiz}]"
```

```
ic gorur: dis fonksiyondan
ana kabuk gorur: [tanimsiz]
```

`ic` fonksiyonu `gizli` adını hiç tanımlamadığı hâlde gördü, çünkü çağrı zinciri üzerinde
o ad etkindi. Programlama Temelleri kursunda tanıtılan ad arama sırasında bu görünürlük
oluşmazdı; orada bir adın hangi tanıma bağlandığı, kodun yazıldığı yere bakılarak
belirlenirdi.

Pratik sonuç: fonksiyonlarınızın davranışı, kendilerinin görmediği bir çağrıcının yerel
değişkenlerine bağlı hâle gelebilir. Bunu önlemenin yolu, fonksiyonların ihtiyaç duyduğu
her değeri argüman olarak almasıdır.

#### `local` ve Çıkış Kodu

`local` bir komuttur ve kendi çıkış kodunu üretir. Bildirimle atamayı aynı satırda
yapmak, sağdaki komutun kodunu görünmez kılar:

```sh
basarisiz() { return 3; }
dene1() { local d; d=$(basarisiz); echo "ayri atama        -> $?"; }
dene2() { local d=$(basarisiz);    echo "local ile birlikte -> $?"; }
dene1; dene2
```

```
ayri atama        -> 3
local ile birlikte -> 0
```

İkinci fonksiyonda görülen `0`, `local` komutunun kendi kodudur. Katı kip altında bu,
başarısız bir komutun betiği durdurmamasına yol açar — Hata Yönetimi dersinde aynı tuzak
yeniden ele alınacak. Kural: **bildirim ile değer atama ayrı satırlara yazılır.**

#### Alt Kabuk Gövdeli Fonksiyonlar

Gövde süslü ayraç yerine parantezle yazılırsa fonksiyon bir alt kabukta çalışır. Dizin
değiştirme, `IFS` ayarlama ya da kabuk seçeneği açma gibi yan etkiler çağırana sızmaz:

```sh
gecici() ( cd /tmp && pwd )
```

Bedeli bir süreç yaratma maliyeti ve genel değişken atamalarının kaybolmasıdır. Yalıtım
gerçekten gerekiyorsa doğru araçtır.

#### Betiğe Uygulama

`rapor.sh`, tekrar eden işleri fonksiyonlara alır:

```sh
BETIK_ADI="rapor.sh"

hata()  { printf '%s: hata: %s\n'  "$BETIK_ADI" "$*" >&2; }
uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; }
oldur() { hata "$1"; exit "${2:-1}"; }
bolum() { printf '\n== %s ==\n' "$*"; }

sinif_adi() {
  case "$1" in
    2) echo "basarili"       ;;
    3) echo "yonlendirme"    ;;
    4) echo "istemci hatasi" ;;
    5) echo "sunucu hatasi"  ;;
    *) echo "tanimsiz"; return 1 ;;
  esac
}

alan_sayimi() {
  local alan="$1" dosya="$2"
  cut -d' ' -f"$alan" "$dosya" | sort | uniq -c | sort -rn
}
```

Sınamalar tek satıra iner ve hata kodları anlamlarını korur:

```sh
kutuk=erisim.log

[[ "$adet" =~ ^[1-9][0-9]*$ ]] || oldur "-n icin pozitif tam sayi bekleniyor: $adet" 2
[ -f "$kutuk" ] || oldur "duz dosya degil ya da yok: $kutuk" 3
[ -r "$kutuk" ] || oldur "okuma izni yok: $kutuk" 3
[ -s "$kutuk" ] || uyari "kutuk bos: $kutuk"
```

Parçalar bir araya geldiğinde betik şöyledir:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
# Kullanim: rapor.sh [-n SAYI] [KUTUK]

BETIK_ADI="rapor.sh"

hata()  { printf '%s: hata: %s\n'  "$BETIK_ADI" "$*" >&2; }
uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; }
oldur() { hata "$1"; exit "${2:-1}"; }
bolum() { printf '\n== %s ==\n' "$*"; }

sinif_adi() {
  case "$1" in
    2) echo "basarili"       ;;
    3) echo "yonlendirme"    ;;
    4) echo "istemci hatasi" ;;
    5) echo "sunucu hatasi"  ;;
    *) echo "tanimsiz"; return 1 ;;
  esac
}

alan_sayimi() {
  local alan="$1" dosya="$2"
  cut -d' ' -f"$alan" "$dosya" | sort | uniq -c | sort -rn
}

adet=5

while getopts ':n:' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    :)  oldur "-$OPTARG deger bekliyor" 2 ;;
    \?) oldur "bilinmeyen secenek -$OPTARG" 2 ;;
  esac
done
shift $((OPTIND - 1))

kutuk="${1:-erisim.log}"

[[ "$adet" =~ ^[1-9][0-9]*$ ]] || oldur "-n icin pozitif tam sayi bekleniyor: $adet" 2
[ -f "$kutuk" ] || oldur "duz dosya degil ya da yok: $kutuk" 3
[ -r "$kutuk" ] || oldur "okuma izni yok: $kutuk" 3
[ -s "$kutuk" ] || uyari "kutuk bos: $kutuk"

bolum "Kutuk: $kutuk"

bolum "En cok istenen $adet yol"
alan_sayimi 7 "$kutuk" | head -"$adet"

bolum "Durum kodu siniflari"
cut -d' ' -f9 "$kutuk" | cut -c1 | sort | uniq -c |
  while read -r sayi sinif; do
    printf '  %sxx %-17s%3d\n' "$sinif" "$(sinif_adi "$sinif")" "$sayi"
  done
```

```sh
chmod +x rapor.sh
./rapor.sh -n 3
```

```

== Kutuk: erisim.log ==

== En cok istenen 3 yol ==
      6 /api/veri
      4 /yok.html
      4 /index.html

== Durum kodu siniflari ==
  2xx basarili          18
  3xx yonlendirme        3
  4xx istemci hatasi     6
  5xx sunucu hatasi      3
```

```sh
./rapor.sh yok.log; echo "kod=$?"
```

```
rapor.sh: hata: duz dosya degil ya da yok: yok.log
kod=3
```

`oldur` fonksiyonu `exit` çağırdığı için betik orada durur; `hata` ve `uyari` yalnızca
yazar ve kararı çağırana bırakır. Üç fonksiyonun bu biçimde ayrılması, ilerideki
derslerde katı kip eklenirken tek dokunuş noktası sağlayacak.

#### Özet

- Kabuk fonksiyonu adlandırılmış bir komuttur: konumsal parametrelerle çağrılır, çıkış
  kodu döndürür, sonucunu standart çıktısına yazar. `$0` fonksiyon içinde değişmez.
- `return` çıkış kodu belirler ve yalnızca fonksiyondan çıkar; `exit` betiği sonlandırır.
- Değer döndürmenin yolu standart çıktıya yazmaktır; bu nedenle değer döndüren fonksiyon
  uyarılarını standart hataya yazmak zorundadır.
- Değişkenler öntanımlı olarak geneldir; `local` bildirimi adı fonksiyona bağlar ve her
  geçici değişken için yazılmalıdır.
- Kabuğun kapsamı dinamiktir: `local` bir ad, çağrılan fonksiyonlara da görünür.
- `local d=$(komut)` yazımı komutun çıkış kodunu gizler; bildirim ile atama ayrılır.

#### Sonraki Adım

Rapordaki her sayım, kütüğü baştan sona bir kez daha okuyor. Beş sayım için beş tarama
yapılıyor. Bunu tek taramaya indirmek, ara sonuçları bellekte tutmayı gerektirir. Sonraki
ders dizileri ve ilişkisel dizileri ele alır; ikincisinin hangi bash sürümünden itibaren
bulunduğu ve bulunmadığında ne yapılacağı da orada gösterilecek.

### Diziler ve İlişkisel Diziler

Source: https://academia.sh/tr/kurslar/kabuk-programlama/diziler-ve-iliskisel-diziler

Dizinli ve ilişkisel dizilerin tanımı, toplu genişletme, seyreklik, tek geçişte sayım ve sürüm bağımlılığının denetlenmesi.

Rapordaki her sayım kütüğü baştan sona bir kez daha okuyor: beş sayım için beş tarama.
Küçük dosyalarda bu fark edilmez, kütük büyüdükçe belirginleşir. Tek taramaya inmek, ara
sonuçları bellekte tutmayı gerektirir.

Veri Yapıları kursunda bu iş için iki yapı tanımlanmıştı: dizinle erişilen **dizi** ve
anahtarla erişilen **karma tablosu**. Kabuk her ikisinin de bir biçimini sunar. Bu ders
onları, sözdizim tuzaklarını ve — ilişkisel diziler için kaçınılmaz olan — sürüm
bağımlılığını ele alır.

#### Dizinli Diziler

Dizi, parantez içinde kelime listesiyle tanımlanır. Dizinler sıfırdan başlar.

```sh
siniflar=(2 3 4 5)
echo "ilk=${siniflar[0]} son=${siniflar[3]} sayi=${#siniflar[@]}"
```

```
ilk=2 son=5 sayi=4
```

Süslü ayraçlar zorunludur: `$siniflar[0]` yazımı, `siniflar` değişkeninin değerinin
ardına `[0]` metnini ekler. Ayraçsız yazımda `$siniflar` tek başına **ilk elemanı** verir
— tüm diziyi değil.

#### Toplu Genişletme

Diziyi bütün olarak genişletmenin iki biçimi vardır ve fark, argüman listelerindeki
`"$@"`–`"$*"` ayrımıyla aynıdır.

```sh
say() { printf 'sayi=%d\n' "$#"; printf '  <%s>\n' "$@"; }
yollar=("/api/veri" "/urun/12" "iki kelimeli yol")
say "${yollar[@]}"
say "${yollar[*]}"
say ${yollar[@]}
```

```
sayi=3
  </api/veri>
  </urun/12>
  <iki kelimeli yol>
sayi=1
  </api/veri /urun/12 iki kelimeli yol>
sayi=5
  </api/veri>
  </urun/12>
  <iki>
  <kelimeli>
  <yol>
```

Kural değişmedi: **`"${dizi[@]}"` yazılır.** Tırnaksız biçim elemanları böler, `[*]`
biçimi hepsini tek metne indirger.

Diziler fonksiyonlara doğrudan geçirilemez; kabukta fonksiyon argümanları yalnızca
kelime listesidir. Aktarım genişletmeyle yapılır:

```sh
yollar=("/api/veri" "/urun/12" "iki kelimeli yol")
yaz() { printf 'aldigim: %s\n' "$@"; }
yaz "${yollar[@]}"
```

```
aldigim: /api/veri
aldigim: /urun/12
aldigim: iki kelimeli yol
```

Bu, dizinin kopyalanarak geçtiği anlamına gelir; fonksiyon içindeki değişiklikler çağırana
dönmez.

#### Ekleme, Dizinler ve Seyreklik

`+=` işleci diziye sona ekler. `${!dizi[@]}` kullanılan dizinleri verir.

```sh
yollar=("/api/veri" "/urun/12" "iki kelimeli yol")
yollar+=("/panel")
echo "sayi=${#yollar[@]} dizinler=${!yollar[@]}"
```

```
sayi=4 dizinler=0 1 2 3
```

Kabuk dizileri **seyrek (sparse)** olabilir: bir eleman silindiğinde kalanlar kaymaz ve
dizinlerde boşluk oluşur.

```sh
yollar=("/api/veri" "/urun/12" "iki kelimeli yol" "/panel")
unset 'yollar[1]'
echo "sayi=${#yollar[@]} dizinler=${!yollar[@]}"
echo "son eleman=${yollar[-1]}"
```

```
sayi=3 dizinler=0 2 3
son eleman=/panel
```

Bu, dizi üzerinde `for ((i = 0; i < ${#dizi[@]}; i++))` biçiminde dönmenin neden yanlış
olduğunu gösterir: eleman sayısı 3, ama geçerli dizinler 0, 2 ve 3'tür. Doğru yazımlar
`for e in "${dizi[@]}"` ya da `for i in "${!dizi[@]}"` biçimindedir.

`unset 'yollar[1]'` yazımındaki tırnak gereklidir; tırnaksız bırakılırsa `[1]` bir yol adı
kalıbı olarak yorumlanabilir.

Dilimleme, `${dizi[@]:baslangic:adet}` biçimiyle yapılır:

```sh
tam=(a b c d e)
echo "1..3: ${tam[@]:1:3}"
```

```
1..3: b c d
```

#### Komut Çıktısından Dizi Kurmak

`mapfile` (eşanlamlısı `readarray`) bir akışı satır satır diziye okur. `-t` seçeneği satır
sonlarını atar.

```sh
mapfile -t kodlar < <(cut -d' ' -f9 erisim.log | sort -u)
echo "farkli kod sayisi=${#kodlar[@]} -> ${kodlar[*]}"
```

```
farkli kod sayisi=8 -> 200 201 301 302 304 403 404 500
```

`dizi=( $(komut) )` biçiminde yazmak yaygın ama yanlıştır: çıktı kelime bölmeye ve yol
adı genişletmesine uğrar, boşluk içeren satırlar parçalanır. `mapfile` bu iki işlemi
uygulamaz. Bash 4.0 ve sonrasında bulunur.

#### İlişkisel Diziler

`declare -A` bildirimi, anahtarı dizgi olan bir dizi yaratır. Bu, kabuğun karma tablosu
karşılığıdır ve tek geçişte sayım yapmayı mümkün kılar.

```sh
declare -A sayim
while read -r kod; do
  sayim["$kod"]=$(( ${sayim["$kod"]:-0} + 1 ))
done < <(cut -d' ' -f9 erisim.log)

for k in "${!sayim[@]}"; do printf '%s -> %s\n' "$k" "${sayim[$k]}"; done | sort
```

```
200 -> 17
201 -> 1
301 -> 1
302 -> 1
304 -> 1
403 -> 2
404 -> 4
500 -> 3
```

`${sayim["$kod"]:-0}` gösterimi, ilk kez görülen anahtarın sıfırdan başlamasını sağlar;
öntanımlı değer gösterimlerinin dizi elemanlarında da çalıştığını gösterir.

Anahtarların sırası tanımsızdır — karma tablosunun doğal sonucudur. Sıralı çıktı
isteniyorsa `sort` gerekir; yukarıdaki örnekte bu yüzden vardır.

Bir anahtarın varlığı `-v` testiyle sınanır:

```sh
declare -A sayim
while read -r kod; do
  sayim["$kod"]=$(( ${sayim["$kod"]:-0} + 1 ))
done < <(cut -d' ' -f9 erisim.log)

[[ -v sayim[404] ]] && echo "404 anahtari var"
[[ -v sayim[999] ]] || echo "999 anahtari yok"
```

```
404 anahtari var
999 anahtari yok
```

Anahtar başına toplam almak da aynı desendir. Aşağıdaki döngü, her yola gönderilen toplam
bayt sayısını tek taramada hesaplar:

```sh
declare -A bayt
while read -r yol b; do
  bayt["$yol"]=$(( ${bayt["$yol"]:-0} + b ))
done < <(cut -d' ' -f7,10 erisim.log)

for y in "${!bayt[@]}"; do printf '%8d %s\n' "${bayt[$y]}" "$y"; done | sort -rn | head -5
```

```
   36120 /panel
   25230 /urun/12
   21990 /urun/45
   20480 /index.html
    7236 /api/veri
```

#### Sürüm Bağımlılığı

İlişkisel diziler bash'in 4.0 sürümünden itibaren bulunur. Daha eski bir bash'te
`declare -A` tanınmaz — ve sonuç, sessiz olduğu için tehlikelidir.

```sh
#!/usr/bin/env bash
declare -A sayim
sayim[404]=4
echo "404 -> ${sayim[404]}"
```

Bash 4 ve sonrasında:

```
404 -> 4
```

Bash 3.2'de:

```
iliskisel.sh: line 2: declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
404 -> 4
```

Betik hata iletisi bastı ama **çalışmaya devam etti ve doğru görünen bir sonuç üretti.**
Nedeni şudur: bildirim başarısız olunca `sayim` sıradan bir dizinli dizi oldu ve `404`
anahtarı sayısal bir dizin olarak yorumlandı. Anahtarlar tam sayı olduğu sürece sonuç
tesadüfen doğru çıkar.

Anahtarlar dizgi olduğunda gizlenme biter:

```
metin-anahtar.sh: line 3: /api/veri: syntax error: operand expected (error token is "/api/veri")
```

Dizinli dizi, köşeli ayraç içindeki metni aritmetik ifade sayar ve yol adını
değerlendiremez.

Çözüm, gereksinimi betiğin başında sınamaktır. `BASH_VERSINFO` dizisi sürüm bileşenlerini
sayısal olarak tutar:

```sh
#!/usr/bin/env bash
# surum-sinama.sh
if ((BASH_VERSINFO[0] < 4)); then
  echo "bu betik bash 4 veya ustunu gerektirir (bulunan: $BASH_VERSION)" >&2
  exit 4
fi
declare -A sayim
sayim[404]=4
echo "404 -> ${sayim[404]}"
```

Betik, dördüncü sürümden eski bir yorumlayıcıyla çağrıldığında hemen durur; örneğin sürümü
3.2'de kalmış bir sistem kabuğunda `bu betik bash 4 veya ustunu gerektirir (bulunan:
3.2.57(1)-release)` iletisini yazar ve 4 koduyla çıkar. Bash 4 ve üstünde sınama geçilir
ve betik çalışmaya devam eder:

```sh
/bin/bash surum-sinama.sh
echo "cikis kodu: $?"
```

```
404 -> 4
cikis kodu: 0
```

Bu sınama, hatayı ortaya çıktığı yerde değil kaynağında bildirir. Belirli bir sürümü
gerektiren her betikte bulunmalıdır.

#### POSIX Kabuğunda Dizi Yoktur

POSIX kabuk dili ne dizinli ne ilişkisel dizi tanımlar. Tek liste yapısı konumsal
parametrelerdir ve `set --` ile doldurulabilir:

```sh
set -- 2 3 4 5
echo "sayi=$#  ilk=$1  son=$4"
for s in "$@"; do printf '%s ' "$s"; done; echo
```

```
sayi=4  ilk=2  son=5
2 3 4 5 
```

Tek bir liste taşınabilir ve betiğin kendi argümanlarını ezer; ciddi bir kısıttır.

Anahtar başına sayım için POSIX çözümü kabukta veri yapısı kurmak değil, işi bunun için
tasarlanmış bir araca devretmektir. Alan tabanlı işleme aracının bir satırı, bu dersteki
döngünün tamamının yerini tutar ve her yerde aynı çalışır:

```sh
awk '{ sayim[$9]++ } END { for (k in sayim) print k, sayim[k] }' erisim.log | sort
```

```
200 17
201 1
301 1
302 1
304 1
403 2
404 4
500 3
```

Bu araç Metin İşleme konusunda ayrıntılı ele alınacak. Buradaki ders, seçimle ilgilidir:
**kabukta veri yapısı kurmak, verinin kabukta kalması gerektiğinde anlamlıdır;** yalnızca
sayım yapılacaksa dış araç hem daha hızlı hem daha taşınabilirdir.

#### Özet

- Dizi elemanlarına `${dizi[i]}` ile erişilir; ayraçsız `$dizi` yalnızca ilk elemanı verir.
- `"${dizi[@]}"` elemanları ayrı kelimeler olarak genişletir; `[*]` tek metne indirger,
  tırnaksız biçim böler.
- Kabuk dizileri seyrek olabilir; eleman sayısıyla dizinler örtüşmeyebileceği için sayaçlı
  döngü yerine `"${!dizi[@]}"` kullanılır.
- `mapfile -t` bir akışı bölme ve genişletme uygulamadan diziye okur.
- İlişkisel diziler bash 4.0'dan itibaren bulunur; eksik olduğunda bildirim başarısız olur
  ama betik çalışmaya devam eder. `BASH_VERSINFO` ile sürüm sınaması zorunludur.
- POSIX kabuk dilinde dizi yoktur; anahtar başına sayım için alan tabanlı araç kullanılır.

#### Sonraki Adım

Bu dersteki sayımlarda `$(( ... ))` ve `$( ... )` gösterimleri gerekçelendirilmeden
kullanıldı. Sonraki ders bu iki gösterimi tanımlar: kabukta aritmetiğin neden yalnızca tam
sayı olduğunu, ondalıklı hesabın nasıl yapıldığını, komut çıktısının değere çevrilirken
hangi karakterlerin kaybolduğunu ve bu dönüşümün maliyetini.

### Aritmetik ve Komut Yerine Koyma

Source: https://academia.sh/tr/kurslar/kabuk-programlama/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.

Ö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.

```sh
echo "$(( 7 / 2 )) $(( -7 / 2 )) $(( 7 % 2 )) $(( 2 ** 10 ))"
```

```
3 -3 1 1024
```

Bölme **sıfıra doğru kırpar**: $7/2$ için 3, $-7/2$ için $-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:

```sh
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:

```sh
# dene.sh
echo "$(( 08 ))"
```

```
dene.sh: line 2: 08: value too great for base (error token is "08")
```

`08` sekizlik tabanda geçerli değildir. Tabanı açıkça bildirmek gerekir:

```sh
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).

```sh
(( 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:

```sh
(( 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:

```sh
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.

```sh
awk 'BEGIN { printf "%.2f\n", 4 / 30 * 100 }'
```

```
13.33
```

Keyfi 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:

```sh
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:

```sh
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.

```sh
adet=$(wc -l < erisim.log)
echo "[$adet]"
```

```
[30]
```

Bu çıktıda baştaki boşluk yoktur, çünkü GNU `wc` dosyayı standart girdiden okurken sayıyı
dolgusuz yazar. BSD kökenli `wc` (macOS) aynı komutta sayıyı boşlukla hizalar ve `[      30]`
üretir; birinci derste değinilen bu fark, sayıyı metin olarak karşılaştıran betiklerde
sessizce hataya yol açar. Aritmetik genişletme her iki durumda da boşlukları soğurur:

```sh
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.

```sh
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:

```sh
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:

```sh
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:

```sh
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:

```sh
printf 'merhaba\n' > tek.txt
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:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
# Kullanim: rapor.sh [-n SAYI] [KUTUK]

BETIK_ADI="rapor.sh"

hata()  { printf '%s: hata: %s\n'  "$BETIK_ADI" "$*" >&2; }
uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; }
oldur() { hata "$1"; exit "${2:-1}"; }
bolum() { printf '\n== %s ==\n' "$*"; }

sinif_adi() {
  case "$1" in
    2) echo "basarili"       ;;
    3) echo "yonlendirme"    ;;
    4) echo "istemci hatasi" ;;
    5) echo "sunucu hatasi"  ;;
    *) echo "tanimsiz"; return 1 ;;
  esac
}

alan_sayimi() {
  local alan="$1" dosya="$2"
  cut -d' ' -f"$alan" "$dosya" | sort | uniq -c | sort -rn
}

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 ))
}

adet=5

while getopts ':n:' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    :)  oldur "-$OPTARG deger bekliyor" 2 ;;
    \?) oldur "bilinmeyen secenek -$OPTARG" 2 ;;
  esac
done
shift $((OPTIND - 1))

kutuk="${1:-erisim.log}"

[[ "$adet" =~ ^[1-9][0-9]*$ ]] || oldur "-n icin pozitif tam sayi bekleniyor: $adet" 2
[ -f "$kutuk" ] || oldur "duz dosya degil ya da yok: $kutuk" 3
[ -r "$kutuk" ] || oldur "okuma izni yok: $kutuk" 3
[ -s "$kutuk" ] || uyari "kutuk bos: $kutuk"

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 ))

bolum "En cok istenen $adet yol"
alan_sayimi 7 "$kutuk" | head -"$adet"

bolum "Durum kodu siniflari"
cut -d' ' -f9 "$kutuk" | cut -c1 | sort | uniq -c |
  while read -r sayi sinif; do
    printf '  %sxx %-17s%3d  %%%s\n' "$sinif" "$(sinif_adi "$sinif")" "$sayi" "$(yuzde "$sayi" "$istek")"
  done
```

```sh
chmod +x rapor.sh
./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.

## Metin İşleme

Düzenli ifadeler, satır filtreleme, akış editörüyle dönüştürme, alan tabanlı işleme, sıralama, tekilleştirme ve sütun düzeyi dönüşümler.

### Düzenli İfadeler

Source: https://academia.sh/tr/kurslar/kabuk-programlama/duzenli-ifadeler

Temel ve genişletilmiş sözdizim ayrımı, yineleyiciler, çapa ve köşeli ayraç ifadeleri, açgözlülük ve taşınabilir karakter sınıfları.

Betik şu ana dek yolları `cut` ile ayırdı ve durum kodlarını sabit dizgilerle saydı. İki
yaklaşım da verinin biçiminin kusursuz olduğunu varsayar. Gerçek kütüklerde bozuk satır,
eksik alan ve beklenmedik biçim bulunur; ayrıca "dört yüz ile başlayan kodlar" gibi
kalıplar sabit dizgiyle ifade edilemez.

**Düzenli ifade (regular expression)**, bir dizgi kümesini betimleyen kalıp dilidir.
Algoritmalar kursunun metin algoritmaları konusunda örüntü arama sabit bir dizgi için
tanımlanmıştı; düzenli ifade aynı problemi bir dizgi yerine sonsuz olabilen bir küme için
kurar.

Bu ders kalıp dilini kurar. Sonraki dersler onu araçlara bağlar.

#### İki Sözdizim

POSIX iki düzenli ifade sözdizimi tanımlar ve araçlar hangisini kullandığını seçenekle
bildirir.

**Temel düzenli ifade (basic regular expression, BRE)**, `grep` ve `sed` komutlarının
öntanımlı sözdizimidir. Gruplama, seçenek ve bazı yineleyiciler ters bölüyle yazılır.

**Genişletilmiş düzenli ifade (extended regular expression, ERE)**, `grep -E` ve `awk`
tarafından kullanılır. Aynı işleçler ters bölüsüz yazılır.

```sh
grep -c  '\(404\|500\)' erisim.log
grep -Ec  '(404|500)'   erisim.log
```

```
7
7
```

İki kalıp aynı kümeyi betimler; yalnızca yazımları farklıdır. Bu kursta ERE yeğlenecek:
okunması daha kolaydır ve ters bölü yığılmasını önler.

Kalıplar **tek tırnak içinde** yazılır. Kalıp dilindeki `$`, `*`, `\`, `[` karakterleri
kabuk için de anlamlıdır; tek tırnak bunların kabuk tarafından yorumlanmasını engeller.
Bu, Değişkenler ve Tırnak Kuralları dersindeki kuralın doğrudan uygulanmasıdır.

#### Harfi Harfine Eşleşme ve Nokta

Özel anlamı olmayan her karakter kendisiyle eşleşir. `.` ise **herhangi bir tek
karakterle** eşleşir — bu, en sık gözden kaçan özel karakterdir.

```sh
printf '%s\n' 'stil.css' 'stilXcss' 'stil-css' > adlar.txt
grep 'stil.css' adlar.txt
```

```
stil.css
stilXcss
stil-css
```

Nokta kaçırılınca yalnızca gerçek nokta eşleşir:

```sh
grep 'stil\.css' adlar.txt
```

```
stil.css
```

Dosya adı, alan adı ve IP adresi gibi nokta içeren verilerde bu ayrım doğruluk
meselesidir.

#### Yineleyiciler

Yineleyici, kendinden önceki öğenin kaç kez tekrarlanacağını belirtir.

| ERE | BRE | Anlamı |
|---|---|---|
| `*` | `*` | sıfır veya daha çok |
| `+` | `\+` | bir veya daha çok |
| `?` | `\?` | sıfır veya bir |
| `{n}` | `\{n\}` | tam olarak `n` |
| `{n,m}` | `\{n,m\}` | en az `n`, en çok `m` |

```sh
printf '%s\n' 'renk' 'renkk' 'renkkk' > yin.txt
```

```sh
grep -Ec 'renk+'    yin.txt
grep -Ec 'renk{2}'  yin.txt
grep -Ec 'renk{1,2}' yin.txt
```

```
3
2
3
```

`renk{2}` iki satırda eşleşti: `renkk` ve `renkkk` — ikincisinde ilk iki `k` yeterlidir,
çünkü kalıbın satırın tamamını kaplaması gerekmez.

Bu son cümle kritiktir. **Düzenli ifade, satırın herhangi bir yerinde eşleşme arar.**
Tam satır eşleşmesi isteniyorsa çapa kullanılır.

#### Çapalar

`^` satırın başını, `$` satırın sonunu belirtir. İkisi de karakter değil **konum**
eşler; genişlikleri yoktur.

```sh
grep -c '^10\.0\.0' erisim.log
```

```
14
```

İkisi birlikte kullanıldığında kalıp satırın tamamını kaplar. Kütükteki her satırın
beklenen biçimde olduğunu doğrulamak bu yolla yapılır:

```sh
KALIP='^[0-9.]+ - - \[[^]]+\] "[A-Z]+ [^ ]+ HTTP/[0-9.]+" [0-9]{3} [0-9]+$'
grep -Ec "$KALIP" erisim.log
```

```
30
```

Otuz satırın tamamı kalıba uydu. Kütüğe bozuk satırlar karıştığında `-v` seçeneği onları
ayıklar:

```sh
KALIP='^[0-9.]+ - - \[[^]]+\] "[A-Z]+ [^ ]+ HTTP/[0-9.]+" [0-9]{3} [0-9]+$'
{ cat erisim.log
  echo 'bozuk satir'
  echo '10.0.0.1 - - [x] "GET /a HTTP/1.1" 200 5'
} > tumu.log
grep -Ev "$KALIP" tumu.log
```

```
bozuk satir
```

İki bozuk satır eklendi ama kalıp yalnızca birini yakaladı. İkincisinin zaman damgası
gerçekçi değil, buna karşın `\[[^]]+\]` parçası "köşeli ayraçlar arasında en az bir
karakter" dediği için kalıba uyuyor. Kalıp, betimlediği kadarını doğrular; alan sayısına
dayanan tamamlayıcı bir sınama Alan Tabanlı İşleme dersinde eklenecek.

Bu kalıp değişkende tutulduğu için **çift tırnakla** genişletilmiştir; tek tırnak
kullanılsaydı değişken adı harfi harfine geçerdi. Kalıbın kendisi atama sırasında tek
tırnak içindeydi.

#### Köşeli Ayraç İfadeleri

Köşeli ayraç, bir karakter kümesi tanımlar ve içindeki tüm karakterlerden **birini**
eşler.

- `[abc]` — üç harften biri
- `[0-9]` — aralık
- `[^0-9]` — kümede olmayan (değilleme)
- `[.-]` — köşeli ayraç içinde çoğu özel karakter anlamını yitirir

```sh
cut -d' ' -f9 erisim.log | grep -c '^[^2]'
cut -d' ' -f9 erisim.log | grep -Ec '^[45]'
```

```
12
9
```

Otuz istekten on ikisi başarı sınıfı dışında, dokuzu hata sınıfındadır.

Değilleme yalnızca ayracın **ilk** konumunda özel anlam taşır; `[a^b]` üç karakterden
birini eşler. Kısa çizgi de aynı kuraldadır: son konumdaysa harfi harfinedir.

#### Taşınabilir Karakter Sınıfları

POSIX, adlandırılmış karakter sınıfları tanımlar. Köşeli ayraç içinde yazılırlar ve
kendileri de ayraçlıdır:

| Sınıf | Karşılığı |
|---|---|
| `[[:digit:]]` | rakamlar |
| `[[:alpha:]]` | harfler |
| `[[:alnum:]]` | harf ve rakamlar |
| `[[:space:]]` | boşluk türleri |
| `[[:upper:]]`, `[[:lower:]]` | büyük ve küçük harfler |

```sh
grep -Eo '^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+' erisim.log | sort -u
```

```
10.0.0.12
10.0.0.31
10.0.2.7
172.16.0.9
192.168.1.5
```

`-o` seçeneği satırın tamamını değil, yalnızca eşleşen bölümü yazar.

Bazı gerçekleştirimler `\d`, `\w`, `\s` gibi kısayollar sunar. Bunlar POSIX'te tanımlı
değildir; destekleyen ve desteklemeyen araçlar birlikte bulunur. Aynı kalıbı iki farklı
sistemde çalıştırdığınızda farklı sonuç almanın en yaygın nedeni budur. **Taşınabilir
betikte `[[:digit:]]` yazılır.**

Adlandırılmış sınıfların ikinci üstünlüğü, `[a-z]` gibi aralıkların yerel ayara bağlı
olmasıdır: sıralama düzeni değiştiğinde aralığın kapsadığı karakterler de değişir.
`[[:lower:]]` bu belirsizliği taşımaz.

#### Açgözlülük

Yineleyiciler **açgözlüdür (greedy)**: eşleşebilecekleri en uzun metni alırlar.

```sh
echo 'a"bir" ve "iki"z' | grep -Eo '".*"'
echo 'a"bir" ve "iki"z' | grep -Eo '"[^"]*"'
```

```
"bir" ve "iki"
"bir"
"iki"
```

İlk kalıptaki `.*`, iki tırnak arasındaki her şeyi yuttu. İkinci kalıp `[^"]*` yazımıyla
"tırnak olmayan karakterler" dediği için her alıntıyı ayrı eşledi.

Yaygın gerçekleştirimlerde açgözlü olmayan yineleyici (`*?` gibi) **yoktur**; POSIX'in
son baskısı bunu tanımlasa da araçlar uygulamaz. Çözüm her
zaman aynıdır: sınırlayıcıyı değilleyen bir karakter sınıfı kullanmak. Bu kalıp — açılış
sınırlayıcı, sınırlayıcı olmayanların yinelenmesi, kapanış sınırlayıcı — alan ayıklamanın
temel biçimidir ve kütükteki köşeli ayraçlı zaman damgasını almak için de kullanılır.

#### Gruplama ve Geri Başvuru

Parantez bir alt kalıbı gruplar; hem yineleyici uygulamak hem de eşleşen metni saklamak
için kullanılır. `\1`, `\2` … saklanan metne **geri başvurur (backreference)**.

```sh
printf '%s\n' 'aa' 'ab' 'abab' > tekrar.txt
grep -E '(.)\1'  tekrar.txt
grep -E '(..)\1' tekrar.txt
```

```
aa
abab
```

Birinci kalıp "aynı karakterin iki kez yinelenmesi"ni, ikincisi "aynı iki karakterin iki
kez yinelenmesi"ni betimler. Geri başvuru, düzenli ifadelerin biçimsel dil kuramındaki
tanımının ötesine geçtiği noktadır: geri başvurulu kalıplar düzenli dil sınıfını aşar ve
eşleştirme maliyeti üstel olabilir.

Grupların asıl kullanımı, eşleşen bölümleri yerine koymadır; bu, Akış Editörü ile
Dönüştürme dersinde ele alınacak.

#### Kalıp Yazma Yöntemi

Karmaşık kalıplar tek seferde yazılmaz. İzlenecek sıra şudur:

1. Eşleşmesi istenen satırlardan birini alın ve harfi harfine arayın.
2. Değişken bölümleri sırayla sınıf ve yineleyiciyle değiştirin; her adımda `grep -c`
   ile eşleşme sayısını gözleyin.
3. Sayı beklenenden büyükse kalıp fazla geniştir; küçükse bir durumu kaçırıyordur.
4. Kalıbı `-o` ile çalıştırıp eşleşen bölümü gözle doğrulayın.
5. Son adımda çapa ekleyip tam satır eşleşmesine geçin.

Yukarıdaki satır doğrulama kalıbı bu yolla kurulmuştur. Her adımda ölçülebilir bir sayı
üretmek, kalıbı sınamanın en ucuz yoludur.

#### Özet

- Düzenli ifade bir dizgi kümesini betimler; POSIX iki sözdizim tanımlar ve araçlar
  hangisini kullandığını seçenekle bildirir.
- Kalıplar tek tırnak içinde yazılır; `.` herhangi bir karakteri eşler ve gerçek nokta
  için kaçırılmalıdır.
- Kalıp satırın herhangi bir yerinde eşleşir; tam satır eşleşmesi `^` ve `$` çapalarıyla
  kurulur.
- Adlandırılmış karakter sınıfları taşınabilirdir ve yerel ayardan etkilenmez; `\d` gibi
  kısayollar POSIX'te tanımlı değildir.
- Yineleyiciler açgözlüdür ve yaygın araçlarda açgözlü olmayan biçim yoktur; çözüm
  sınırlayıcıyı değilleyen karakter sınıfıdır.

#### Sonraki Adım

Kalıp dili kuruldu; şimdi onu bir araca bağlamak gerekiyor. Sonraki ders satır
filtrelemeyi ele alır: eşleşen satırları seçmenin ötesinde saymak, tersini almak, yalnızca
eşleşen bölümü yazmak, dosya adlarını listelemek ve `grep`'in çıkış kodunu bir koşul
olarak kullanmak.

### Satır Filtreleme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/satir-filtreleme

Örüntüye göre satır seçme, sayma ve tersini alma, sözcük ve tam satır eşleşmesi, sabit dizgi kipi, çok dosyalı arama ve çıkış kodunun koşul olarak kullanımı.

Kalıp dili kuruldu. Onu uygulayan ilk araç, bir akıştan kalıba uyan satırları seçen
süzgeçtir. Bu araç kabuk metin işlemenin en çok çağrılanıdır ve seçeneklerinin çoğu,
"eşleşen satırları yaz" davranışını başka bir soruya dönüştürür.

Bu ders o soruları ve her birinin doğru seçeneğini ele alır.

#### Seçmek, Saymak, Konumlandırmak

Öntanımlı davranış, eşleşen satırları yazmaktır. `-n` seçeneği satır numarasını ekler:

```sh
grep -n '500' erisim.log
```

```
8:192.168.1.5 - - [07/Feb/2024:09:17:22 +0000] "GET /api/veri HTTP/1.1" 500 180
13:192.168.1.5 - - [07/Feb/2024:09:21:38 +0000] "GET /api/veri HTTP/1.1" 500 180
27:10.0.2.7 - - [07/Feb/2024:09:35:44 +0000] "POST /api/veri HTTP/1.1" 500 180
```

`-c` yalnızca sayıyı verir. Sayılan şey **eşleşen satır sayısıdır**, eşleşme sayısı
değildir:

```sh
grep -Ec  '\.' erisim.log
grep -Eo '\.' erisim.log | wc -l
```

```
30
131
```

Otuz satırın hepsinde en az bir nokta var; toplam nokta sayısı 131. Ayrım, "kaç istek 404
aldı" ile "404 dizgisi kaç kez geçti" sorularını ayırır. Eşleşme sayısı isteniyorsa `-o`
ile her eşleşme ayrı satıra dökülür ve sayılır.

`grep -c ''` yazımı, boş kalıp her satırla eşleştiği için satır sayımı verir ve `wc -l`
gibi hizalama boşluğu üretmez:

```sh
grep -c '' erisim.log
```

```
30
```

#### Tersini Almak

`-v` seçeneği eşleşmeyen satırları seçer. Süzme işlerinde en çok kullanılan seçenektir;
gürültü satırlarını atmanın doğrudan yoludur.

```sh
grep -vc '" 200 ' erisim.log
```

```
13
```

Otuz istekten on üçü `200` dışında bir kodla yanıtlanmış. `-v` ile `-c` birlikte
kullanılabilir; `-v` ile `-o` birlikte anlamsızdır, çünkü eşleşmeyen satırda yazılacak
eşleşme yoktur.

#### Eşleşmenin Sınırlarını Daraltmak

Üç seçenek, kalıbın satırın hangi bölümüyle eşleşeceğini kısıtlar.

`-i` büyük–küçük harf ayrımını kaldırır. `-w` kalıbın **sözcük sınırlarında** başlayıp
bitmesini ister. `-x` kalıbın **satırın tamamıyla** eşleşmesini ister.

```sh
printf '%s\n' 'GET /a' 'get /b' 'GETTER /c' > kucukbuyuk.txt
grep -c  'GET' kucukbuyuk.txt
grep -ic 'GET' kucukbuyuk.txt
grep -wc 'GET' kucukbuyuk.txt
```

```
2
3
1
```

Dosya `GET /a`, `get /b` ve `GETTER /c` satırlarını içeriyor. Öntanımlı arama ikisini,
harf duyarsız arama üçünü, sözcük araması yalnızca birini seçti — `GETTER` içindeki `GET`
sözcük değildir.

`-x`, çapa yazmanın kısayoludur:

```sh
printf '%s\n' '404' '4040' > kod.txt
grep -c  '404' kod.txt
grep -xc '404' kod.txt
```

```
2
1
```

Dosyadaki `4040` satırı, çapasız kalıba uydu. Tam eşleşme isteyen sınamalarda `-x`
unutulursa sonuç sessizce fazla çıkar.

#### Sabit Dizgi Kipi

`-F` seçeneği kalıbı düzenli ifade olarak değil, harfi harfine dizgi olarak yorumlar.

```sh
printf '%s\n' 'a.b' 'axb' > nokta.txt
grep -c  'a.b' nokta.txt
grep -Fc 'a.b' nokta.txt
```

```
2
1
```

`-F` iki nedenle önemlidir. Birincisi doğruluk: kalıp bir değişkenden — kullanıcı
argümanından, dosya adından, yapılandırma değerinden — geliyorsa içindeki `.`, `*`, `[`
karakterleri kalıp olarak yorumlanır ve arama beklenenden geniş sonuç verir. İkincisi,
kullanıcı girdisinin kalıp olarak kullanılması bir güvenlik sınırıdır: hazırlanmış bir
kalıp, eşleştirme maliyetini kasıtlı olarak patlatabilir.

Kural: **kalıbın kendisini siz yazmadıysanız `-F` kullanın.**

Kalıplar bir dosyada tutulabilir; `-f` seçeneği her satırı ayrı kalıp sayar:

```sh
printf '%s\n' '403' '500' > kaliplar.txt
grep -Fcf kaliplar.txt erisim.log
```

```
5
```

#### Çok Dosyalı Arama

Birden çok dosya verildiğinde çıktının başına dosya adı eklenir:

```sh
mkdir -p kutuk-dizin
head -10 erisim.log > kutuk-dizin/pazartesi.log
tail -10 erisim.log > kutuk-dizin/sali.log
grep -c '404' kutuk-dizin/*.log
```

```
kutuk-dizin/pazartesi.log:1
kutuk-dizin/sali.log:2
```

`-h` bu öneki bastırır, `-H` tek dosyada bile ekler. Betikte çıktı ayrıştırılacaksa
seçeneği açıkça yazmak gerekir; öneki dosya sayısına göre değişen bir çıktı, kırılgan bir
dayanaktır.

`-l` eşleşme bulunan dosyaların adını, `-L` bulunmayanların adını verir:

```sh
grep -l '500' kutuk-dizin/*.log
```

```
kutuk-dizin/pazartesi.log
kutuk-dizin/sali.log
```

`-r` bir dizin ağacında özyinelemeli arar. Dosyaların ziyaret sırası dosya sisteminin
verdiği sıradır ve gerçekleştirimden gerçekleştirime değişir; sıralı çıktı gerekiyorsa
sonuç ayrıca `sort` ile sıralanmalıdır.

`-m N` her dosyada ilk `N` eşleşmeden sonra durur. Büyük dosyalarda "var mı" sorusunun
yanıtı için tüm dosyayı okumayı önler.

#### Bağlam Satırları

`-A N` eşleşmeden sonraki, `-B N` önceki, `-C N` her iki yandaki `N` satırı da yazar.
Bloklar arasına `--` ayıracı konur.

```sh
grep -A 1 '" 500 ' erisim.log | head -4
```

```
192.168.1.5 - - [07/Feb/2024:09:17:22 +0000] "GET /api/veri HTTP/1.1" 500 180
10.0.0.12 - - [07/Feb/2024:09:18:00 +0000] "GET /api/veri HTTP/1.1" 200 3300
--
192.168.1.5 - - [07/Feb/2024:09:21:38 +0000] "GET /api/veri HTTP/1.1" 500 180
```

Hata ayıklamada bir hatanın öncesindeki ve sonrasındaki isteklere bakmak için kullanılır.
Ayıraç satırı çıktının biçimini bozduğu için, çıktısı başka bir araca akacak hatlarda
bağlam seçenekleri kullanılmaz.

#### Çıkış Kodunu Koşul Olarak Kullanmak

`-q` seçeneği hiçbir şey yazmaz; ilk eşleşmede durur ve sonucu yalnızca çıkış koduyla
bildirir. Betikte "var mı" sorusunun doğru biçimi budur:

```sh
if grep -q '" 500 ' erisim.log; then
  echo "sunucu hatasi var"
fi
```

```
sunucu hatasi var
```

`grep -c ... > /dev/null` yazımı aynı işi görmez: `-c` eşleşme olmasa da `0` yazar ve
çıkış kodu yine eşleşme durumuna bağlıdır, ama tüm dosya okunur. `-q` ilk eşleşmede
durur.

Çıkış Kodları dersinde kurulan üç kodlu sözleşme burada işler: `0` bulundu, `1`
bulunamadı, `2` dosya hatası. Katı kip altında çalışan betiklerde "bulunamadı" durumunun
betiği durdurmaması için koşul olarak kullanılmalı ya da `|| true` eklenmelidir.

#### Betiğe Uygulama

`rapor.sh` iki yeni bölüm kazanır. Birincisi, kütükteki bozuk satırları sayar; ikincisi
hata döndüren yolları listeler.

```sh
#!/usr/bin/env bash
# rapor.sh
BETIK_ADI="rapor.sh"
uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; }
bolum() { printf '\n== %s ==\n' "$*"; }

kutuk="${1:-erisim.log}"

SATIR_KALIBI='^[0-9.]+ - - \[[^]]+\] "[A-Z]+ [^ ]+ HTTP/[0-9.]+" [0-9]{3} [0-9]+$'

bozuk=$(grep -Evc "$SATIR_KALIBI" "$kutuk")
if [ "$bozuk" -gt 0 ]; then
  uyari "$bozuk satir beklenen bicimde degil ve sayimlara dahil"
fi

bolum "Hata donduren yollar"
grep -E '" [45][0-9]{2} ' "$kutuk" | cut -d' ' -f7 | sort | uniq -c | sort -rn
```

Blok, `uyari` ve `bolum` yardımcılarını da içerir ve kütük adını ilk argümandan alır;
böylece tek başına çalışır. Bozuk satırlı bir kütükle çalıştırıldığında:

```sh
{ cat erisim.log; echo 'bozuk satir'; } > tumu.log
chmod +x rapor.sh
./rapor.sh tumu.log
```

Uyarı standart hataya düşer, rapor standart çıktıya devam eder. Bozuk satır hata kodu
taşımadığı için hata döndüren yollar bölümü temiz kütükle aynıdır:

```
rapor.sh: uyari: 1 satir beklenen bicimde degil ve sayimlara dahil

== Hata donduren yollar ==
      4 /yok.html
      3 /api/veri
      2 /gizli
```

`grep -E '" [45][0-9]{2} '` kalıbındaki tırnak ve boşluklar, durum kodu alanını
kütükteki başka bir sayıdan ayırır. Bu, alan kavramının kalıpla taklit edilmesidir;
sonraki derslerde alan tabanlı araçlar aynı işi konumla yapacak ve kalıba gerek
kalmayacak.

#### Filtrelemenin Hattaki Yeri

Boru hattı dersinde eşzamanlılık kurulmuştu. Bunun pratik sonucu bir sıralama kuralıdır:
**veriyi küçülten adımı hattın başına koyun.**

```sh
grep -E '" [45][0-9]{2} ' "$kutuk" | cut -d' ' -f7 | sort | uniq -c
```

Bu hatta `grep` otuz satırı dokuza indirir; `sort` dokuz satır sıralar. Sıralama sonda
olsaydı otuz satır sıralanırdı. Sıralama maliyetinin girdi büyüklüğüne bağlı olması —
Algoritmalar kursunda gösterildiği gibi — bu kuralın gerekçesidir.

Kural bir istisna taşır: filtreleme, sonraki adımın ihtiyaç duyduğu satırları
atmamalıdır. Bağlam gerektiren dönüşümlerde süzme sonraya bırakılır.

#### Özet

- `-c` eşleşen satırları sayar, eşleşmeleri değil; eşleşme sayısı `-o` ile satırlara
  dökülüp sayılır.
- `-v` seçimi tersine çevirir; `-w` sözcük, `-x` tam satır eşleşmesi ister.
- `-F` kalıbı harfi harfine yorumlar ve dışarıdan gelen kalıplarda zorunludur.
- Çok dosyalı aramada çıktı öneki dosya sayısına göre değişir; betikte `-h` ya da `-H`
  açıkça yazılır.
- `-q` hiçbir şey yazmaz ve ilk eşleşmede durur; betikte varlık sınamasının doğru
  biçimidir.
- Veriyi küçülten filtre, boru hattının başına konur.

#### Sonraki Adım

Filtreleme satırları seçer ama değiştirmez. Kütükteki zaman damgasını yeniden
biçimlendirmek, alan sınırlayıcılarını sadeleştirmek ya da bir alanı gizlemek için satırın
kendisini dönüştürmek gerekir. Sonraki ders akış editörünü ele alır: yerine koyma, adres
ve aralık seçimi, geri başvurularla yeniden yazma ve yerinde düzenleme seçeneğinin
gerçekleştirimler arasındaki uyumsuzluğu.

### Akış Editörü ile Dönüştürme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/akis-editoru-ile-donusturme

Yerine koyma komutu ve bayrakları, geri başvurularla yeniden yazma, adres ve aralık seçimi, yerinde düzenlemenin gerçekleştirimler arasındaki uyumsuzluğu.

Filtreleme satırları seçer ama değiştirmez. Kütükteki zaman damgasından yalnızca saati
almak, sayısal kimlik içeren yolları tek bir kalıba indirmek ya da satırı ayrılmış bir
kayda çevirmek için satırın kendisini dönüştürmek gerekir.

**Akış editörü (stream editor)**, girdiyi satır satır okuyup her satıra bir komut dizisi
uygulayan ve sonucu yazan süzgeçtir. Etkileşimli bir editörden farkı, kararların önceden
verilmiş olmasıdır: dosya açılmaz, hiçbir şey görüntülenmez, akış yalnızca akar.

#### Yerine Koyma

En sık kullanılan komut `s`'dir: `s/kalıp/yerine/bayraklar`.

```sh
head -2 erisim.log | sed 's/HTTP\/1.1/HTTP/'
```

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP" 200 5120
10.0.0.31 - - [07/Feb/2024:09:12:51 +0000] "GET /statik/stil.css HTTP" 200 2048
```

Kalıpta eğik çizgi geçtiği için kaçırılması gerekti. Ayırıcı serbesttir; `s` komutundan
sonra gelen ilk karakter ayırıcı sayılır:

```sh
head -1 erisim.log | sed 's|HTTP/1.1|HTTP|'
```

Yol içeren kalıplarda `|` veya `#` seçmek, ters bölü yığılmasını ortadan kaldırır.

Öntanımlı davranış, **her satırda yalnızca ilk eşleşmeyi** değiştirmektir. Bayraklar bunu
değiştirir:

```sh
echo 'a-b-c-d' | sed 's/-/+/'
echo 'a-b-c-d' | sed 's/-/+/g'
echo 'a-b-c-d' | sed 's/-/+/3'
```

```
a+b-c-d
a+b+c+d
a-b-c+d
```

`g` bayrağı tüm eşleşmeleri, sayı bayrağı yalnızca `N`'inci eşleşmeyi değiştirir. `I`
bayrağı büyük–küçük harf ayrımını kaldırır. POSIX bu özelliği son baskısında küçük harfli
biçimiyle tanımladı; buradaki büyük harfli yazım bir gerçekleştirim eklentisidir.

#### Eşleşen Metni Yeniden Kullanmak

Yerine koyma bölümünde `&` tüm eşleşmeyi temsil eder:

```sh
head -1 erisim.log | sed -E 's/ [0-9]{3} [0-9]+$/[&]/'
```

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1"[ 200 5120]
```

Gruplar `\1`, `\2` … ile geri çağrılır. Bu, satırı istenen biçimde yeniden yazmanın
yoludur:

```sh
sed -E 's/^([0-9.]+) - - \[([^]]+)\] "([A-Z]+) ([^ ]+) [^"]*" ([0-9]{3}) ([0-9]+)$/\1|\3|\4|\5|\6/' erisim.log | head -3
```

```
10.0.0.12|GET|/index.html|200|5120
10.0.0.31|GET|/statik/stil.css|200|2048
10.0.0.12|GET|/urun/12|200|8410
```

Boşlukla ayrılmış ve tırnak içeren kütük satırı, boru işaretiyle ayrılmış beş alanlı bir
kayda dönüştü. İkinci grup (zaman damgası) yakalandı ama kullanılmadı; yakalamak
zorunludur, çünkü kalıp satırın tamamını kaplamalıdır.

#### Sözdizim Seçimi

`sed` öntanımlı olarak temel düzenli ifade sözdizimini kullanır. `-E` seçeneği
genişletilmiş sözdizime geçer.

Temel sözdizimde bazı işleçlerin desteği gerçekleştirime bağlıdır:

```sh
echo 'aaa' | sed 's/a\+/X/'
echo 'aaa' | sed -E 's/a+/X/'
```

```
X
X
```

GNU sürümünde iki komut da çalışır; temel sözdizimde `\+` işleci bir uzantı olarak
tanınır. BSD sürümünde (macOS) ise `\+` tanınmaz: birinci komut kalıbı "bir `a` ve bir
artı işareti" diye okur, eşleşme bulamaz ve `aaa` satırını değiştirmeden yazar.

Sonuç: **`+`, `?`, `|` ve `{n,m}` kullanılacaksa `-E` yazılır.** `-E` seçeneği hem BSD hem
GNU sürümlerinde bulunur; eski GNU betiklerinde görülen `-r` seçeneği ise yalnızca GNU'ya
özgüdür.

#### Adresler

Komutun önüne yazılan **adres**, komutun hangi satırlara uygulanacağını belirler.

| Adres | Anlamı |
|---|---|
| `5` | beşinci satır |
| `$` | son satır |
| `/kalıp/` | kalıba uyan satırlar |
| `2,4` | ikinci ile dördüncü satır arası |
| `/a/,/b/` | `a` eşleşen satırdan `b` eşleşene kadar |
| `adres!` | adresin **dışındaki** satırlar |

`d` komutu satırı siler. `bir`, `iki`, `uc`, `dort` satırlarını içeren bir dosyada:

```sh
printf 'bir\niki\nuc\ndort\n' > s.txt
sed '2,3d' s.txt
```

```
bir
dort
```

```sh
sed '$d' s.txt
```

```
bir
iki
uc
```

```sh
sed '/ik/d' s.txt
```

```
bir
uc
dort
```

```sh
sed '/ik/!d' s.txt
```

```
iki
```

`!` işareti seçimi tersine çevirir; son çağrı "eşleşmeyenleri sil" diyerek yalnızca
eşleşeni bıraktı.

`p` komutu satırı yazar. Akış editörü öntanımlı olarak her satırı zaten yazdığı için `p`
tek başına yineleme üretir; `-n` seçeneği öntanımlı yazımı kapatır ve `p` seçici hâle
gelir:

```sh
sed -n '2,3p' s.txt
```

```
iki
uc
```

Bu ikili — `-n` ve `p` — filtrelemenin akış editörüyle yazılmış biçimidir. Yalnızca satır
seçmek için süzgeç aracı daha uygundur; dönüştürme ile seçim birlikte gerekiyorsa akış
editörü tek geçişte ikisini birden yapar.

#### Diğer Komutlar

`y/abc/xyz/` karakter karşılıklarını dönüştürür; kalıp değil, birebir eşleme kullanır:

```sh
echo 'abc' | sed 'y/abc/xyz/'
```

```
xyz
```

`q` komutu, adresine ulaşıldığında akışı sonlandırır: `sed '100q'` ilk yüz satırı yazıp
durur ve dosyanın kalanını okumaz.

Birden çok komut `-e` ile ya da noktalı virgülle zincirlenir:

```sh
head -1 erisim.log | sed -e 's/HTTP\/1.1//' -e 's/  */ /g'
```

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html " 200 5120
```

Komutlar her satıra **sırayla** uygulanır; ikinci komut, birincinin çıktısı üzerinde
çalışır. Bu, bir yerine koymanın diğerini besleyebileceği anlamına gelir ve sıra
değiştiğinde sonuç değişir.

#### Yerinde Düzenleme: Taşınabilir Değil

`-i` seçeneği dosyayı yerinde değiştirir. Bu seçeneğin argüman alışı iki büyük
gerçekleştirimde uyumsuzdur ve bu, kabuk betiklerindeki en yaygın taşınabilirlik
hatalarından biridir.

GNU sürümünde sonek **seçeneğe bitişik** yazılır ve isteğe bağlıdır; sonek verilmezse
yedek alınmaz:

```sh
sed -i 's/bir/BIR/' s.txt
cat s.txt
```

```
BIR
iki
uc
dort
```

BSD sürümünde (macOS) `-i` **zorunlu olarak** ayrı bir yedek soneki argümanı ister. Aynı
komut orada `unterminated substitute pattern` hatasıyla düşer: seçenek, ardından gelen
`s/bir/BIR/` dizisini yedek soneki sandı ve `s.txt` adını komut olarak ayrıştırmaya
çalıştı. BSD'deki doğru yazım boş soneki açıkça vermektir:
`sed -i '' 's/bir/BIR/' s.txt`.

GNU sürümünde bu yazım ise çalışmaz: ayrı bir boş argüman verilirse o argüman betik
sayılır ve ardından gelen dizgi dosya adı sanılır:

```sh
sed -i '' 's/bir/BIR/' s.txt
```

```
sed: can't read s/bir/BIR/: No such file or directory
```

İki yazımdan hiçbiri her iki sürümde çalışmaz. Betiklerde `-i` kullanmamak, taşınabilirlik
gereken her durumda doğru karardır. Yerine, Yönlendirme dersinde kurulan desen yazılır:

```sh
sed 's/bir/BIR/' s.txt > s.yeni && mv s.yeni s.txt
```

Bu desen üç üstünlük taşır: her yerde aynı çalışır, `sed` başarısız olursa özgün dosya
bozulmaz ve `mv` aynı dosya sisteminde atomiktir — dosyayı okuyan başka bir süreç ya eski
ya yeni içeriği görür, yarım hâlini değil.

`-i` seçeneğinin kendisi de bunu yapar: geçici dosyaya yazıp yer değiştirir. Sağladığı tek
şey yazım kısalığıdır ve bedeli taşınabilirliktir.

#### Betiğe Uygulama

Kütükteki yolların bir bölümü sayısal kimlik içeriyor: `/urun/12` ve `/urun/45` ayrı ayrı
sayıldığında ikisi de listenin altında kalır. Kimlikleri tek bir simgeye indirmek, hangi
**uç noktanın** yoğun olduğunu görünür kılar:

```sh
kutuk=erisim.log

cut -d' ' -f7 "$kutuk" | sed -E 's#/[0-9]+#/:id#g' | sort | uniq -c | sort -rn
```

```
      6 /urun/:id
      6 /api/veri
      4 /yok.html
      4 /index.html
      3 /statik/stil.css
      3 /panel
      2 /gizli
      1 /giris
      1 /eski-sayfa
```

`/urun/:id` altı istekle listenin başına çıktı; ayrı sayıldığında üçer istekle dördüncü ve
beşinci sıradaydı. Normalleştirme, sayımın neyi ölçtüğünü değiştirir — bu bir biçim
düzeltmesi değil, çözümleme kararıdır ve rapor çıktısında belirtilmelidir.

İkinci uygulama, zaman damgasından saat alanını çıkarır:

```sh
kutuk=erisim.log

sed -E 's/^.*:([0-9]{2}):[0-9]{2}:[0-9]{2} .*$/\1/' "$kutuk" | sort | uniq -c
```

```
     30 09
```

Örnek kütüğün tamamı tek bir saat dilimine düştüğü için tek satır çıktı; günlere yayılan
gerçek bir kütükte bu, yük dağılımını veren bir sütun üretir.

Kalıptaki `^.*:` bölümü açgözlüdür ve **son** iki nokta üst üsteye kadar ilerler; sonraki
gruplar bu yüzden dakika ve saniyeyi eşler. Açgözlülüğün burada işe yaraması rastlantı
değildir, ama kalıbın doğruluğu ona bağlıdır — açgözlülüğe dayanan kalıplar kırılgandır
ve alan tabanlı bir çözüm daha güvenlidir.

#### Özet

- Yerine koyma `s/kalıp/yerine/bayrak` biçimindedir; ayırıcı serbesttir ve öntanımlı
  davranış satır başına bir eşleşmedir.
- `&` tüm eşleşmeyi, `\1`, `\2` grupları temsil eder; satırı yeniden yazmanın aracı budur.
- Temel sözdizimde `\+` gibi işleçlerin desteği gerçekleştirime bağlıdır; `-E` her iki
  yaygın sürümde bulunur ve yeğlenir.
- Adres komutun kapsamını belirler; `!` seçimi tersine çevirir, `-n` ile `p` seçici yazma
  sağlar.
- `-i` seçeneğinin argüman alışı BSD ve GNU sürümlerinde uyumsuzdur; taşınabilir yazım
  geçici dosyaya yazıp yer değiştirmektir.

#### Sonraki Adım

Akış editörü satırı bir metin olarak görür; alanları ancak kalıpla taklit edebilir. Kütük
gibi sütunlu verilerde bu, kırılgan kalıplara yol açar. Sonraki ders alan tabanlı işlemeyi
ele alır: satırı otomatik olarak alanlara bölen, alanlar üzerinde hesap yapabilen ve
anahtar başına birikim tutabilen aracı — böylece önceki konuda ilişkisel dizilerle yazılan
sayımlar tek satıra inecek.

### Alan Tabanlı İşleme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/alan-tabanli-isleme

Kayıt ve alan modeli, kalıp–eylem yapısı, yerleşik değişkenler, ilişkisel dizilerle birikim ve tek geçişte rapor üretimi.

Akış editörü satırı bir metin olarak görür; alanları ancak kalıpla taklit edebilir.
Kütükteki dokuzuncu alanı almak için satırın tamamını betimleyen bir kalıp yazmak
gerekmişti ve o kalıp, biçim küçük bir değişikliğe uğradığında bozulur.

Sütunlu veri için ayrı bir araç sınıfı vardır. **Alan tabanlı işleme aracı**, girdiyi
kayıtlara, kayıtları alanlara böler ve kalıp–eylem çiftlerinden oluşan bir program
yürütür. Bu ders o modeli kurar ve önceki konuda ilişkisel dizilerle yazılan sayımları
tek satıra indirir.

#### Kayıt ve Alan Modeli

Girdi öntanımlı olarak satırlara — **kayıtlara** — bölünür. Her kayıt, boşluk kümelerine
göre **alanlara** ayrılır. `$1` birinci alanı, `$0` kaydın tamamını, `$NF` son alanı
verir.

```sh
head -2 erisim.log | awk '{ print NR, NF, $1, $9, $NF }'
```

```
1 10 10.0.0.12 200 5120
2 10 10.0.0.31 200 2048
```

Yerleşik değişkenler modelin çevresini tamamlar:

| Değişken | İçerik |
|---|---|
| `NR` | okunan kayıt sayısı (dosyalar boyunca birikir) |
| `FNR` | o dosyadaki kayıt sırası |
| `NF` | o kayıttaki alan sayısı |
| `FS` | alan ayırıcısı (öntanımlı: boşluk kümeleri) |
| `OFS` | çıktı alan ayırıcısı (öntanımlı: tek boşluk) |
| `FILENAME` | okunmakta olan dosyanın adı |

Öntanımlı ayırıcının "boşluk kümeleri" olması önemlidir: art arda gelen boşluklar tek
ayırıcı sayılır ve satır başındaki boşluklar yok sayılır. Bu, `cut` komutundan ayrıldığı
noktadır — `cut` her ayırıcıyı ayrı sayar ve hizalanmış metinlerde boş alanlar üretir.

#### Kalıp ve Eylem

Program, `kalıp { eylem }` çiftlerinden oluşur. Kayıt kalıba uyuyorsa eylem çalışır.

```sh
awk '$9 >= 500 { print $1, $7, $9 }' erisim.log
```

```
192.168.1.5 /api/veri 500
192.168.1.5 /api/veri 500
10.0.2.7 /api/veri 500
```

İki bölüm de isteğe bağlıdır ve eksik olan yerine varsayılan konur:

- Kalıp yazılmazsa eylem **her kayda** uygulanır.
- Eylem yazılmazsa varsayılan eylem `{ print }` olur.

```sh
awk '$9 == 404' erisim.log | grep -c ''
```

```
4
```

Kalıp bir düzenli ifade de olabilir. `/kalıp/` tüm kaydı, `$9 ~ /kalıp/` belirli bir alanı
sınar:

```sh
awk '$9 ~ /^[45]/ { n++ } END { print n }' erisim.log
```

Programın tamamı **tek tırnak içinde** yazılır. Programda `$` işareti alan başvurusudur;
çift tırnak kullanılırsa kabuk onu değişken genişletmesi sanar ve program bozulur. Bu,
Değişkenler ve Tırnak Kuralları dersindeki kuralın en sık gerekli olduğu yerdir.

#### `BEGIN` ve `END`

`BEGIN` bloğu ilk kayıt okunmadan, `END` bloğu son kayıttan sonra çalışır. Başlık yazmak,
ayar yapmak ve birikimleri raporlamak için kullanılır.

```sh
awk '
  BEGIN { print "adres            bayt" }
  { t += $10 }
  END { printf "toplam: %d bayt, %d istek, ortalama %.1f\n", t, NR, t / NR }
' erisim.log
```

```
adres            bayt
toplam: 117780 bayt, 30 istek, ortalama 3926.0
```

Değişkenler bildirilmeden kullanılır; ilk kullanımda sayısal bağlamda sıfır, dizgi
bağlamında boş dizi değerindedirler. `t += $10` satırı bu yüzden ayrı bir ilklendirme
gerektirmedi.

`printf` biçimlendirmesi, kabuğun `printf` komutuyla aynı belirteçleri kullanır ve
ondalıklı sayı basabilir — kabuk aritmetiğinin tam sayı kısıtı burada yoktur.

#### İlişkisel Diziler

Diziler yalnızca ilişkiseldir ve bildirim gerektirmez. Anahtar başına sayım, önceki
konudaki kabuk döngüsünün tek satırlık karşılığıdır:

```sh
awk '{ sayim[$7]++ } END { for (y in sayim) printf "%3d %s\n", sayim[y], y }' erisim.log \
  | sort -rn | head -5
```

```
  6 /api/veri
  4 /yok.html
  4 /index.html
  3 /urun/45
  3 /urun/12
```

Anahtar başına toplam da aynı desendir:

```sh
awk '{ bayt[$7] += $10 } END { for (y in bayt) printf "%8d %s\n", bayt[y], y }' erisim.log \
  | sort -rn | head -5
```

```
   36120 /panel
   25230 /urun/12
   21990 /urun/45
   20480 /index.html
    7236 /api/veri
```

`for (anahtar in dizi)` döngüsünün ürettiği sıra tanımsızdır; sıralı çıktı için dışarıdan
`sort` gerekir. Bu, ilişkisel dizilerin karma tablosuyla gerçekleştirilmesinin doğrudan
sonucudur ve Veri Yapıları kursunda tanımlanmıştı.

#### Alan Ayırıcısını Değiştirmek

`-F` seçeneği alan ayırıcısını belirler. Değer tek karakter ya da düzenli ifade olabilir.

```sh
printf 'a:b:c\nd:e:f\n' | awk -F: '{ print $2 }'
```

```
b
e
```

Çıktı ayırıcısı ayrı bir değişkendir:

```sh
head -2 erisim.log | awk 'BEGIN { OFS="|" } { print $1, $7, $9 }'
```

```
10.0.0.12|/index.html|200
10.0.0.31|/statik/stil.css|200
```

`print` komutunda alanlar **virgülle** ayrılır; virgül `OFS` ile yer değiştirir. Virgül
yerine boşluk yazılırsa alanlar ayırıcısız bitiştirilir — bu, dizgi birleştirmenin
yazımıdır ve sessiz bir hata kaynağıdır.

Bir alana atama yapmak kaydı yeniden kurar:

```sh
head -1 erisim.log | awk '{ $1="GIZLI"; print }' | cut -c1-45
```

```
GIZLI - - [07/Feb/2024:09:12:44 +0000] "GET /
```

Kayıt yeniden kurulurken alanlar `OFS` ile birleştirilir; özgün satırdaki çoklu boşluklar
korunmaz. Alan gizleme ve maskeleme işlemlerinde beklenen davranış budur.

#### Sayı ve Dizgi Bağlamı

Alanlar hem sayı hem dizgi olarak yorumlanabilir; hangi yorumun uygulanacağı işlecin
türüne ve işlenenlerin kaynağına bağlıdır. Girdiden gelen bir alan sayı gibi görünüyorsa
sayısal karşılaştırılır; ancak dizgi sabitleriyle karşılaştırıldığında dizgi bağlamı
kazanabilir.

```sh
awk 'BEGIN { x="10"; y="9"; print (x   > y  ) ? "dizgi: 10>9" : "dizgi: 10<9" }'
awk 'BEGIN { x="10"; y="9"; print (x+0 > y+0) ? "sayi: 10>9"  : "sayi: 10<9"  }'
```

```
dizgi: 10<9
sayi: 10>9
```

Belirsizliği ortadan kaldırmanın yolu bağlamı açıkça zorlamaktır: sayısal karşılaştırma
için `+0`, dizgi karşılaştırması için `""` ile birleştirme. Dış kaynaklı veriyle çalışan
programlarda bu alışkanlık, açıklaması güç sonuçları önler.

#### Kabuk Değişkeni Aktarmak

Kabuk değişkenleri program metnine gömülmez; `-v` seçeneğiyle aktarılır.

```sh
esik=400
awk -v esik="$esik" '$9 >= esik { n++ } END { print n " istek " esik " ve uzeri" }' erisim.log
```

```
9 istek 400 ve uzeri
```

Değeri program metnine gömmek — tırnakları kapatıp açarak — iki nedenle yanlıştır:
değerdeki tırnak ve ters bölü karakterleri programı bozar ve dışarıdan gelen bir değer
program kodu hâline gelerek istenmeyen komutlar çalıştırabilir. `-v` bu sınırı korur.

#### Bozuk Kayıtları Yakalamak

Alan sayısı, biçim doğrulamasının en ucuz ölçütüdür:

```sh
{ cat erisim.log
  echo 'bozuk satir'
  echo '10.0.0.1 - - [x] "GET /a HTTP/1.1" 200 5'
} > tumu.log
awk 'NF != 10 { print FILENAME ":" FNR ": alan sayisi " NF }' tumu.log
```

```
tumu.log:31: alan sayisi 2
tumu.log:32: alan sayisi 9
```

Düzenli İfadeler dersinde aynı iş satırın tamamını betimleyen bir kalıpla yapılmıştı.
Alan sayısı sınaması daha dar bir doğrulama yapar ama çok daha kısa yazılır ve biçim
değişikliklerine dayanıklıdır. İkisi birlikte kullanılabilir: alan sayısı hızlı eleme,
kalıp ayrıntılı doğrulama içindir.

#### Çok Kalıplı Program

Programda birden çok kalıp–eylem çifti bulunabilir; her kayıt tüm çiftlere karşı sırayla
sınanır.

```sh
awk '
  $9 ~ /^2/     { basarili++ }
  $9 ~ /^[45]/  { hata++; hatali_yol[$7]++ }
  END {
    printf "basarili=%d hata=%d\n", basarili, hata
    for (y in hatali_yol) printf "  %-14s %d\n", y, hatali_yol[y]
  }
' erisim.log | LC_ALL=C sort
```

```
  /api/veri      3
  /gizli         2
  /yok.html      4
basarili=18 hata=9
```

`sort` sıralaması yerel ayara bağlıdır; `LC_ALL=C` çıktıyı bayt sırasına sabitler ve baştaki
boşluklu satırlar `basarili` satırından önce gelir.

Bir kayıt birden çok kalıba uyuyorsa ilgili tüm eylemler çalışır; `case` yapısındaki "ilk
eşleşen dal" kuralı burada geçerli değildir.

#### Betiğe Uygulama

`rapor.sh` şu ana dek her sayım için kütüğü baştan okuyordu: satır sayımı, bayt toplamı ve
dört durum sınıfı için altı ayrı geçiş. Hepsi tek geçişe iner:

```sh
kutuk=erisim.log

awk -v adet="$adet" '
  { istek++; bayt += $10; yol[$7]++; sinif[substr($9, 1, 1)]++ }
  END {
    printf "istek   : %d\n", istek
    printf "bayt    : %d\n", bayt
    printf "ortalama: %.1f bayt\n", (istek ? bayt / istek : 0)
    print ""
    print "durum kodu siniflari:"
    for (s = 2; s <= 5; s++)
      printf "  %sxx %4d  %%%.1f\n", s, sinif[s], (istek ? sinif[s] * 100 / istek : 0)
  }
' "$kutuk"
```

```
istek   : 30
bayt    : 117780
ortalama: 3926.0 bayt

durum kodu siniflari:
  2xx   18  %60.0
  3xx    3  %10.0
  4xx    6  %20.0
  5xx    3  %10.0
```

Üç kazanç var. Kütük bir kez okunuyor. Yüzdeler ondalıklı ve ölçeklendirme hilesi
gerekmiyor. Sıfıra bölme koruması, kabuktaki üçlü işleçle aynı biçimde yazılıyor.

Kayıp da var: rapor mantığının bir bölümü artık kabukta değil, gömülü bir programda.
Betiğin okunurluğu, bu programın kısa ve tek amaçlı kalmasına bağlıdır. İki yüz satırlık
gömülü programlar, kabuk betiğinin sınırının aşıldığının işaretidir.

#### Özet

- Girdi kayıtlara, kayıtlar alanlara bölünür; `$0` kaydın tamamı, `NF` alan sayısı,
  `NR` kayıt sırasıdır. Öntanımlı ayırıcı boşluk kümeleridir.
- Program `kalıp { eylem }` çiftlerinden oluşur; kalıp yoksa her kayda, eylem yoksa
  `print` uygulanır ve kayıt tüm çiftlere karşı sınanır.
- `BEGIN` ve `END` blokları kayıt akışının dışında çalışır; değişkenler bildirimsizdir ve
  sıfırdan başlar.
- İlişkisel dizilerle anahtar başına sayım ve toplam tek satırda yazılır; gezinme sırası
  tanımsızdır.
- Kabuk değerleri program metnine gömülmez, `-v` ile aktarılır.
- Sayı ve dizgi bağlamı işlenenlere bağlıdır; `+0` ve `""` ile açıkça zorlanır.

#### Sonraki Adım

Sayımlar üretildi ama sıralanmaları hep dış bir komuta bırakıldı. Sonraki ders o komutu ve
yanındaki iki aracı ele alır: anahtara göre sıralama, komşu yinelemeleri tekilleştirme ve
iki sıralı akışı küme işlemleriyle karşılaştırma — ayrıca sıralamanın yerel ayara bağlı
olmasının betiklerde neden bir hata kaynağı olduğunu.

### Sıralama, Tekilleştirme ve Birleştirme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/siralama-tekillestirme-ve-birlestirme

Anahtara göre sıralama, yerel ayarın sıralama düzenine etkisi, komşuluk gerektiren tekilleştirme ve sıralı akışlar üzerinde küme işlemleri.

Önceki derslerde her sayım `sort` ile bitirildi ama komutun kendisi açıklanmadı. Sıralama,
kabuk metin işlemenin en çok çağrılan ikinci aracıdır ve iki nedenle ayrı bir ders hak
eder: seçenekleri sonucu tümüyle değiştirir ve yanındaki üç araç — tekilleştirme,
karşılaştırma, birleştirme — girdilerinin sıralı olmasına dayanır.

Algoritmalar kursunda sıralamanın maliyeti ve kararlılık ölçütü tanımlanmıştı. Bu ders o
kavramları bir komutun seçeneklerine bağlar.

#### Sözlük Sırası ve Sayısal Sıra

Öntanımlı karşılaştırma **sözlük sırasıdır**: değerler karakter karakter karşılaştırılır.

```sh
printf '10\n9\n100\n2\n' | sort
```

```
10
100
2
9
```

```sh
printf '10\n9\n100\n2\n' | sort -n
```

```
2
9
10
100
```

Sözlük sırasında `10`, `9`'dan önce gelir çünkü ilk karakteri `1`, `9`'dan küçüktür.
Sayıların sıralandığı her yerde `-n` gerekir; unutulması, raporların en sık görülen
sessiz hatasıdır.

`-r` sırayı tersine çevirir. `-n -r` birlikte, çoktan aza sıralamanın standart yazımıdır.

#### Anahtar Tanımı

`-k` seçeneği, karşılaştırmanın hangi alan üzerinden yapılacağını belirtir. Yazımı
`-k başlangıç,bitiş` biçimindedir ve **bitişin yazılması zorunludur**: `-k2` yazımı
"ikinci alandan satır sonuna kadar" anlamına gelir, `-k2,2` yalnızca ikinci alanı seçer.

```sh
awk '{ print $7, $10 }' erisim.log | sort -k2,2n | head -3
```

```
/eski-sayfa 0
/giris 0
/statik/stil.css 0
```

Sayısal kip anahtara da eklenebilir: `-k2,2n` yazımı yalnızca o anahtar için geçerlidir.
Birden çok `-k` verilirse anahtarlar sırayla uygulanır ve ilk eşitlikte sonraki anahtara
geçilir.

`-t` ayırıcıyı belirler. Öntanımlı ayırıcı, alan tabanlı araçtan farklıdır: boşluk
kümesinden **boşluk olmayana geçiş** noktası ayırıcı sayılır.

```sh
printf 'c:3\na:1\nb:2\n' | sort -t: -k2,2n
```

```
a:1
b:2
c:3
```

#### Kararlılık

Sıralama öntanımlı olarak **kararlı değildir**: eşit anahtarlı satırların göreli sırası
korunmaz. Eşitlik durumunda son çare olarak satırın tamamı karşılaştırılır.

```sh
printf 'b 1\na 1\nc 0\n' | sort -k2,2n
```

```
c 0
a 1
b 1
```

```sh
printf 'b 1\na 1\nc 0\n' | sort -s -k2,2n
```

```
c 0
b 1
a 1
```

`-s` seçeneği son çare karşılaştırmasını kapatır ve girdi sırasını korur. Çok anahtarlı
sıralamalarda — önce şehre, sonra kararlı biçimde tarihe göre — Algoritmalar kursunda
gösterilen kararlılık gereksinimi bu seçenekle karşılanır.

#### Yerel Ayar

Karşılaştırma düzeni, çalışma ortamının yerel ayarına bağlıdır.

```sh
printf 'a\nB\nb\nA\n' | LC_ALL=C sort
```

```
A
B
a
b
```

```sh
printf 'a\nB\nb\nA\n' | LC_ALL=en_US.UTF-8 sort
```

```
a
A
b
B
```

İlk düzen bayt değerine göre, ikincisi harf temeline göre sıralar. İkisi de doğrudur;
sorun, hangisinin uygulanacağının betiği çalıştıran ortama bağlı olmasıdır.

Bu, betiklerde yeniden üretilemez sonuçların başlıca kaynağıdır. Aynı betik iki makinede
farklı sıralı çıktı verir; bir çıktıyı diğeriyle karşılaştıran bir sınama, gerçek bir fark
olmadığı hâlde başarısız olur.

Kural: **betikte sıralama yapılıyorsa yerel ayar açıkça sabitlenir.**

```sh
LC_ALL=C sort erisim.log | head -3
```

```
10.0.0.12 - - [07/Feb/2024:09:12:44 +0000] "GET /index.html HTTP/1.1" 200 5120
10.0.0.12 - - [07/Feb/2024:09:13:02 +0000] "GET /urun/12 HTTP/1.1" 200 8410
10.0.0.12 - - [07/Feb/2024:09:18:00 +0000] "GET /api/veri HTTP/1.1" 200 3300
```

`LC_ALL=C` bayt sırasını seçer; hızlıdır, öngörülebilirdir ve makineler arasında aynıdır.
İnsan okuyucu için harf temelli sıra isteniyorsa yerel ayar yine açıkça yazılmalıdır —
belirsiz bırakılmamalıdır.

Ayrıca `sort -u` ve `uniq` gibi işlemler "eşitlik" tanımını yerel ayardan aldığı için,
farklı ayarlarda farklı sayıda satır üretebilir. Sabitleme bu nedenle yalnızca sıra
meselesi değildir.

#### Tekilleştirme

`uniq`, **komşu** yinelenen satırları teke indirir. Sıralanmamış girdide beklendiği gibi
çalışmaz:

```sh
printf 'a\nb\na\n' | uniq -c
printf 'a\nb\na\n' | sort | uniq -c
```

```
      1 a
      1 b
      1 a
      2 a
      1 b
```

İlk çağrıda hiçbir şey birleştirilmedi çünkü yinelenen `a` satırları komşu değildi. Bu,
kursun ilk konusundan beri kullanılan `sort | uniq -c` deseninin gerekçesidir.

Seçenekler farklı soruları yanıtlar:

| Seçenek | Çıktı |
|---|---|
| `-c` | her satırın önüne yineleme sayısı |
| `-d` | yalnızca birden çok kez geçen satırlar |
| `-u` | yalnızca bir kez geçen satırlar |
| `-i` | büyük–küçük harf ayrımsız karşılaştırma |

```sh
printf 'a\nb\nb\nc\n' | uniq -u
```

```
a
c
```

`sort -u` ile `sort | uniq` aynı sonucu verir ama `sort -u` daha ucuzdur: ayrı bir süreç
yaratılmaz. `uniq -c` gerekiyorsa iki adım zorunludur, çünkü `sort -u` sayım yapmaz.

#### Küme İşlemleri

`comm`, **iki sıralı dosyayı** karşılaştırır ve üç sütun üretir: yalnızca birincide
olanlar, yalnızca ikincide olanlar, ikisinde de olanlar. `-1`, `-2`, `-3` seçenekleri ilgili
sütunu bastırır.

Kütüğün ilk ve son on beş satırında istenen yolları karşılaştıralım. İki dosya da
tekilleştirilmiş ve aynı yerel ayarla sıralanmış olarak hazırlanır:

```sh
export LC_ALL=C
head -15 erisim.log | cut -d' ' -f7 | sort -u > dun.txt
tail -15 erisim.log | cut -d' ' -f7 | sort -u > bugun.txt
```

```sh
comm -23 dun.txt bugun.txt      # yalniz birincide
```

```
/giris
```

```sh
comm -13 dun.txt bugun.txt      # yalniz ikincide
```

```
/eski-sayfa
```

```sh
comm -12 dun.txt bugun.txt      # ortak
```

```
/api/veri
/gizli
/index.html
/panel
/statik/stil.css
/urun/12
/urun/45
/yok.html
```

Üç çağrı sırasıyla küme farkını, ters farkı ve kesişimi verir. İki dosya da sıralı
olmalıdır ve **aynı yerel ayarla** sıralanmış olmalıdır; aksi hâlde `comm` uyarı verir ya
da sessizce yanlış sonuç üretir.

Bu araç, iki kütüğü karşılaştırmanın doğrudan yoludur: hangi yollar yalnızca bu dönemde
istendi, hangi istemciler kayboldu, hangi hata kodları yeni ortaya çıktı.

`join`, iki sıralı dosyayı ortak bir **anahtar alanı** üzerinden birleştirir; ilişkisel
veritabanlarındaki birleştirme işleminin metin karşılığıdır.

Yol başına istek sayısını ve toplam baytı iki ayrı dosyada üretip anahtarlarına göre
sıralayalım:

```sh
awk '{ sayim[$7]++ }     END { for (y in sayim) print y, sayim[y] }' erisim.log | sort -k1,1 > adet.txt
awk '{ bayt[$7] += $10 } END { for (y in bayt)  print y, bayt[y]  }' erisim.log | sort -k1,1 > boyut.txt
join adet.txt boyut.txt | head -5
```

```
/api/veri 6 7236
/eski-sayfa 1 0
/giris 1 0
/gizli 2 580
/index.html 4 20480
```

Öntanımlı olarak her iki dosyanın birinci alanı anahtar sayılır; `-1` ve `-2` seçenekleri
alan numarasını değiştirir, `-t` ayırıcıyı belirler. Girdilerin **anahtar alanına göre**
sıralı olması zorunludur — satırın tamamına göre değil. Bu ayrım, `join` kullanımındaki en
sık hatanın kaynağıdır.

#### Belirlenimci Sıralama

Sayım çıktılarında eşit sayılı satırların sırası, son çare karşılaştırmasına ve yerel
ayara bağlıdır. Bu, raporun her çalıştırmada aynı çıkmasını engelleyebilir:

```sh
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
```

İkincil anahtar açıkça verildiğinde sonuç belirlenimci olur: önce sayıya göre azalan,
eşitlikte ada göre artan.

```sh
cut -d' ' -f7 erisim.log \
  | LC_ALL=C sort | uniq -c | LC_ALL=C sort -k1,1nr -k2,2 | head -5
```

```
      6 /api/veri
      4 /index.html
      4 /yok.html
      3 /panel
      3 /statik/stil.css
```

Eşit sayılı satırlar artık alfabetik sırada. Çıktısı sınanacak ya da iki çalıştırma
arasında karşılaştırılacak her betikte bu yazım kullanılmalıdır.

#### Betiğe Uygulama

`rapor.sh` iki değişiklik alır. Birincisi, tüm sıralamaların yerel ayarı sabitlenir ve
ikincil anahtar verilir:

```sh
export LC_ALL=C

en_cok() {                      # en_cok ALAN DOSYA ADET
  local alan="$1" dosya="$2" adet="$3"
  cut -d' ' -f"$alan" "$dosya" | sort | uniq -c | sort -k1,1nr -k2,2 | head -"$adet"
}
```

`export LC_ALL=C` betiğin başına konur; böylece çağrılan tüm araçlar aynı düzeni kullanır.
Bu, betiğin çıktısını çalıştırıldığı ortamdan bağımsız kılar.

İkincisi, iki kütüğü karşılaştıran bir seçenek eklenir:

```sh
karsilastir() {                 # karsilastir ESKI YENI
  local eski="$1" yeni="$2"
  cut -d' ' -f7 "$eski" | sort -u > "$gecici/eski"
  cut -d' ' -f7 "$yeni" | sort -u > "$gecici/yeni"
  bolum "Yalnizca yeni kutukte gorulen yollar"
  comm -13 "$gecici/eski" "$gecici/yeni"
}
```

Bu fonksiyon geçici dosyalara yazıyor ve onları temizlemiyor. Sağlam Betik Yazımı
konusunda `mktemp` ve tuzaklarla bu eksik giderilecek.

#### Özet

- Öntanımlı karşılaştırma sözlük sırasıdır; sayısal veride `-n` zorunludur.
- `-k başlangıç,bitiş` anahtarı sınırlar; bitiş yazılmazsa anahtar satır sonuna uzanır.
- Sıralama öntanımlı olarak kararlı değildir; `-s` son çare karşılaştırmasını kapatır.
- Karşılaştırma düzeni yerel ayara bağlıdır; betiklerde `LC_ALL=C` ile sabitlenmelidir.
- `uniq` yalnızca komşu yinelemeleri birleştirir; `sort | uniq -c` deseni bu yüzden
  ayrılmazdır.
- `comm` ve `join` sıralı girdi ister; ilki küme işlemleri, ikincisi anahtar üzerinden
  birleştirme yapar.

#### Sonraki Adım

Bu konudaki araçların hepsi satır düzeyinde çalıştı. Sütun düzeyinde kesme, iki dosyayı
yan yana yapıştırma ve büyük bir kütüğü parçalara bölme ayrı bir araç öbeği gerektirir.
Sonraki ders bu öbeği ele alır ve metin işleme konusunu, hangi işin hangi araca ait
olduğunu belirleyen bir seçim ölçütüyle kapatır.

### Kesme, Yapıştırma ve Bölme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/kesme-yapistirma-ve-bolme

Sütun kesme ve sınırları, dosyaları yan yana yapıştırma, karakter düzeyi dönüştürme, dosyayı parçalara bölme ve araç seçimi ölçütü.

Önceki derslerin araçları satır düzeyinde çalıştı: satır seçtiler, satır dönüştürdüler,
satır sıraladılar. Sütun düzeyinde kesme, iki dosyayı yan yana getirme, karakter düzeyi
eşleme ve büyük bir dosyayı parçalara bölme ayrı bir araç öbeği gerektirir.

Bu ders o öbeği tanıtır ve metin işleme konusunu, hangi işin hangi araca ait olduğunu
belirleyen bir ölçütle kapatır.

#### Sütun Kesme

`cut` bu kursta ilk dersten beri kullanılıyor. Üç kipi vardır: bayt kipi (`-b`), karakter kipi
(`-c`) ve alan kipi (`-d` ve `-f`).

```sh
head -2 erisim.log | cut -d' ' -f1,7,9
```

```
10.0.0.12 /index.html 200
10.0.0.31 /statik/stil.css 200
```

```sh
head -2 erisim.log | cut -c1-11
```

```
10.0.0.12 -
10.0.0.31 -
```

Karakter kipi sabit genişlikli çıktılarda — hizalanmış tablolarda, sabit alanlı
kayıtlarda — kullanılır. Alan sayısının değiştiği verilerde işe yaramaz.

`cut` hızlıdır ve yazımı kısadır, ama üç kesin sınırı vardır.

**Ayırıcı tek karakterdir.**

```sh
printf 'a::b::c\n' | cut -d'::' -f2
```

```
cut: the delimiter must be a single character
Try 'cut --help' for more information.
```

Alan tabanlı araç aynı işi düzenli ifadeli ayırıcıyla yapar:

```sh
printf 'a::b::c\n' | awk -F'::' '{ print $2 }'
```

```
b
```

**Ardışık ayırıcılar birleştirilmez.** Her ayırıcı yeni bir alan başlatır:

```sh
printf 'a  b  c\n' | cut -d' ' -f2
printf 'a  b  c\n' | awk '{ print $2 }'
```

```

b
```

`cut` ikinci alanı boş buldu, çünkü iki boşluk arasında boş bir alan var. Alan tabanlı
aracın öntanımlı ayırıcısı boşluk kümesi olduğu için `b` döndü. Hizalanmış ya da elle
yazılmış metinlerde bu fark belirleyicidir.

**Alan sırası değiştirilemez.** İstenen alan numaraları hangi sırayla yazılırsa yazılsın,
çıktı dosyadaki sırayı izler:

```sh
head -1 erisim.log | cut -d' ' -f9,1
```

```
10.0.0.12 200
```

Sıra değiştirmek için alan tabanlı araç gerekir:

```sh
head -1 erisim.log | awk '{ print $9, $1 }'
```

```
200 10.0.0.12
```

Bu üç sınır, `cut` için net bir kullanım alanı bırakır: **tek karakterle ayrılmış, ardışık
ayırıcı içermeyen ve alan sırası değişmeyecek veriler.** Kütük dosyası bu tanıma uyduğu
için kursun ilk konularında kullanıldı.

#### Yan Yana Yapıştırma

`paste`, dosyaların karşılıklı satırlarını birleştirir. `cut` işleminin tersidir.

```sh
cut -d' ' -f7 erisim.log | head -3 > y.txt
cut -d' ' -f9 erisim.log | head -3 > k.txt
paste y.txt k.txt
```

```
/index.html	200
/statik/stil.css	200
/urun/12	200
```

Öntanımlı ayırıcı sekmedir; `-d` değiştirir:

```sh
paste -d: y.txt k.txt
```

```
/index.html:200
/statik/stil.css:200
/urun/12:200
```

`-s` seçeneği her dosyanın satırlarını tek satırda birleştirir; listeyi virgülle ayrılmış
metne çevirmenin kısa yoludur:

```sh
paste -sd, k.txt
```

```
200,200,200
```

`paste`, satırların **konumsal olarak** eşleştiğini varsayar. Bir dosyada satır eksikse
tüm hizalama kayar ve sonuç sessizce yanlış olur. Anahtar üzerinden eşleştirme
gerekiyorsa önceki dersteki `join` kullanılır; `paste` yalnızca sıraya güvenilebilen
verilerde uygundur.

#### Karakter Düzeyi Dönüştürme

`tr`, girdideki karakterleri tek tek eşler. Kalıp tanımaz, alan tanımaz, satır tanımaz —
yalnızca karakter kümeleri üzerinde çalışır ve yalnızca standart girdiden okur.

```sh
printf 'merhaba\n' | tr 'a-z' 'A-Z'
```

```
MERHABA
```

Üç seçenek en sık kullanılanlardır:

```sh
printf 'aaa  bbb\n' | tr -s ' '     # ardisik yinelemeleri teke indir
printf 'a1b2c3\n'   | tr -d '0-9'   # kumedeki karakterleri sil
printf 'a1b2c3\n'   | tr -cd '0-9'  # kumenin disindakileri sil
```

```
aaa bbb
abc
123
```

`tr -s ' '` ile ardışık boşlukları teke indirmek, `cut` kullanılabilir hâle getirmenin
yaygın bir hazırlık adımıdır.

En sık kullanımı, ayırıcıyı satır sonuna çevirerek her alanı ayrı satıra dökmektir:

```sh
tr ' ' '\n' < erisim.log | grep -c '^"GET$'
```

```
27
```

`tr`, kalıp tabanlı araçlardan çok daha ucuzdur çünkü hiçbir şey ayrıştırmaz. Karakter
eşlemesi yeterliyse akış editörü çağırmaya gerek yoktur.

#### Dosyayı Parçalara Bölmek

`split`, büyük bir dosyayı sabit boyutlu ya da sabit satırlı parçalara ayırır.

```sh
mkdir -p parca
split -l 10 erisim.log parca/kutuk-
ls parca
```

```
kutuk-aa
kutuk-ab
kutuk-ac
```

```sh
wc -l parca/*
```

```
  10 parca/kutuk-aa
  10 parca/kutuk-ab
  10 parca/kutuk-ac
  30 total
```

`-l N` satır sayısına, `-b N` bayt sayısına göre böler. Sonek uzunluğu `-a` ile
ayarlanır; öntanımlı iki harf, 676 parçaya kadar yeterlidir.

İki kullanım alanı vardır. Birincisi, bellek sınırına sığmayan bir dosyayı parça parça
işlemek. İkincisi, parçaları ayrı süreçlere dağıtarak koşut işlemek — parçalar bağımsız
işlenebiliyorsa toplam süre parça sayısına bölünür.

Bayt sınırına göre bölmenin satırları ortadan kesebileceğine dikkat edilmelidir; satır
tabanlı veride `-b` yerine `-l` kullanılır.

#### Araç Seçimi

Bu konudaki altı ders, aynı veriye farklı açılardan bakan bir araç ailesi kurdu.
Aralarındaki seçim ölçütü sorunun biçimidir:

| Soru | Araç |
|---|---|
| Hangi satırlar kalıba uyuyor | süzgeç (`grep`) |
| Satırın içindeki metni değiştir | akış editörü (`sed`) |
| Alanlarla hesap yap, anahtar başına birik | alan tabanlı araç (`awk`) |
| Sabit ayırıcılı veriden sütun al | `cut` |
| Sütunları birleştir | `paste`, `join` |
| Karakterleri eşle, sil, sıkıştır | `tr` |
| Sırala, tekilleştir, küme işlemi yap | `sort`, `uniq`, `comm` |

İki genel kural bu tablonun üzerinde durur.

**En dar aracı seçin.** Alan tabanlı araç, tablodaki işlerin hemen hepsini yapabilir; her
şeyi onunla yazmak yine de yanlıştır. Dar araç daha kısa yazılır, daha hızlı çalışır ve
okuyan kişiye niyeti anlatır. `awk '/404/ { print }'` yazımı, `grep 404` ile aynı işi
yapar ama okuyucuya "burada bir hesap var mı" diye düşündürür.

**Bir problemi üç araçtan fazlasıyla çözüyorsanız aracı değiştirin.** Beş halkalı boru
hatları yazılabilir ve bu kursta yazıldı; ama bir betikteki her hat böyleyse, işin kabuk
sınırının ötesine geçtiği düşünülmelidir. Kabuk, süreçleri birbirine bağlamakta
rakipsizdir; karmaşık veri yapıları ve çok adımlı iş mantığı için tasarlanmamıştır.

#### Betiğe Uygulama

Metin işleme konusunun sonunda `rapor.sh`, üç bölümünü de asıl araçlarıyla üretiyor:

```sh
ozet() {                        # ozet KUTUK
  awk '
    { istek++; bayt += $10; sinif[substr($9, 1, 1)]++ }
    END {
      printf "istek   : %d\n", istek
      printf "bayt    : %d\n", bayt
      printf "ortalama: %.1f bayt\n", (istek ? bayt / istek : 0)
      for (s = 2; s <= 5; s++)
        printf "  %sxx %4d  %%%.1f\n", s, sinif[s], (istek ? sinif[s] * 100 / istek : 0)
    }
  ' "$1"
}

en_cok_yol() {                  # en_cok_yol KUTUK ADET
  cut -d' ' -f7 "$1" \
    | sed -E 's#/[0-9]+#/:id#g' \
    | sort | uniq -c | sort -k1,1nr -k2,2 | head -"$2"
}

hata_yollari() {                # hata_yollari KUTUK
  awk '$9 ~ /^[45]/ { n[$7]++ } END { for (y in n) printf "%3d %s\n", n[y], y }' "$1" \
    | sort -k1,1nr -k2,2
}
```

Her fonksiyon tek bir soruyu yanıtlıyor, tek bir veri yolu izliyor ve çıktısı belirlenimci.
Betiğin iskeleti ise hâlâ eksik: hata durumunda yarım rapor üretiyor, geçici dosya
bırakıyor ve elle çalıştırılmayı bekliyor.

#### Özet

- `cut` tek karakterli ayırıcı ister, ardışık ayırıcıları birleştirmez ve alan sırasını
  değiştiremez; bu üç sınır dışında en ucuz sütun aracıdır.
- `paste` dosyaları konumsal olarak yan yana getirir; anahtar eşleştirmesi gerekiyorsa
  `join` kullanılır.
- `tr` karakter kümeleri üzerinde çalışır; kalıp tanımaz ve yalnızca standart girdiden
  okur.
- `split` dosyayı satır ya da bayt sınırına göre parçalar; satır tabanlı veride `-l`
  kullanılır.
- Araç seçiminde en dar araç yeğlenir; bir problem üç araçtan fazlasını gerektiriyorsa
  kabuk sınırının aşıldığı düşünülmelidir.

#### Sonraki Adım

Betik doğru çıktı üretiyor, ama yalnızca her şey yolunda gittiğinde. Bir komut
başarısız olduğunda rapor yarım kalıyor ve çıkış kodu bunu bildirmiyor; tanımsız bir
değişken sessizce boş dizgi olarak genişliyor. Sonraki konu bu boşlukları kapatır ve
katı kip seçenekleriyle başlar: hangi hataların betiği durdurması gerektiğini, hangi
seçeneğin bunu ne ölçüde sağladığını ve seçeneklerin kendi tuzaklarını ele alarak.

## Sağlam Betik Yazımı

Katı kip seçenekleri, sinyal yakalama ve temizlik, izleme kipiyle hata ayıklama, statik çözümleme ve zamanlanmış görevler.

### Hata Yönetimi

Source: https://academia.sh/tr/kurslar/kabuk-programlama/hata-yonetimi

Katı kip seçenekleri, erken çıkışın kapsamı ve istisnaları, tanımsız değişken denetimi, boru hattı kodu ve hata yakalama kancası.

Betik doğru çıktı üretiyor, ama yalnızca her şey yolunda gittiğinde. Bir komut başarısız
olduğunda rapor yarım kalıyor ve betiğin çıkış kodu bunu bildirmiyor. Tanımsız bir
değişken sessizce boş dizgiye genişliyor. Boru hattının ortasındaki hata görünmüyor.

Kabuğun öntanımlı davranışı hataya karşı hoşgörülüdür: bir komut başarısız olsa da
sonraki komut çalışır. Bu, etkileşimli kullanımda doğru bir seçimdir — yanlış yazılmış bir
komut oturumu kapatmamalıdır. Betikte ise ters etki yapar.

**Katı kip (strict mode)**, kabuğun bu hoşgörüsünü kısan seçenekler kümesidir. Bu ders
seçenekleri tek tek ele alır; her birinin ne yakaladığını ve — daha önemlisi — ne
yakalamadığını gösterir.

#### Erken Çıkış

`set -e` seçeneği, çıkış kodu sıfırdan farklı olan bir komuttan sonra kabuğu sonlandırır.

```sh
#!/usr/bin/env bash
# erken.sh
set -e
echo "1: baslangic"
false
echo "2: BU SATIR YAZILMAZ"
```

```
1: baslangic
```

```sh
bash erken.sh > /dev/null
echo $?
```

```
1
```

Betik ikinci satıra ulaşmadan durdu ve başarısızlık kodunu dışarı taşıdı. Kazanç
yalnızca kolaylık değildir: bir betiğin yarım çıktı üretip başarı bildirmesi, onu
çağıran her sistemi yanıltır.

#### Erken Çıkışın İstisnaları

`set -e` bir komutun başarısızlığını her yerde ölümcül saymaz. İstisnalar mantıklıdır ama
sayıları çoktur ve bilinmezse yanlış güven verir.

```sh
#!/usr/bin/env bash
set -e
if false; then echo "a"; fi
echo "1: kosulda basarisizlik betigi durdurmaz"
false || echo "2: || solunda basarisizlik durdurmaz"
false && echo "buraya gelinmez"
echo "3: && solunda basarisizlik da durdurmaz"
false | true
echo "4: boru hattinin son komutu basarili ise durdurmaz"
! false
echo "5: degillenmis komut durdurmaz"
f() { false; echo "f devam etti"; }
if f; then :; fi
echo "6: kosulda cagrilan fonksiyon icinde set -e askiya alinir"
```

```
1: kosulda basarisizlik betigi durdurmaz
2: || solunda basarisizlik durdurmaz
3: && solunda basarisizlik da durdurmaz
4: boru hattinin son komutu basarili ise durdurmaz
5: degillenmis komut durdurmaz
f devam etti
6: kosulda cagrilan fonksiyon icinde set -e askiya alinir
```

Altı istisnanın ilk beşi tasarım gereğidir: koşul olarak kullanılan bir komutun
başarısızlığı bilgi taşır, hata değildir. Altıncısı en tehlikelisidir — bir koşulda
çağrılan fonksiyonun **tüm gövdesi** boyunca erken çıkış askıya alınır ve gövdedeki gerçek
hatalar görünmez olur.

Bu yüzden `set -e` tek başına bir güvence değildir. Kritik komutların kodları ayrıca
sınanmalı ya da bir hata kancası kurulmalıdır.

#### Tanımsız Değişken Denetimi

`set -u` seçeneği, tanımsız bir değişkenin genişletilmesini hata sayar.

```sh
#!/usr/bin/env bash
# u.sh
set -u
echo "kutuk: ${KUTUK}"
```

```
u.sh: line 4: KUTUK: unbound variable
```

Bu seçenek, kabuk betiklerinde en yıkıcı hata sınıfını yakalar. `rm -rf "$DIZIN/"` satırı,
`DIZIN` tanımsızken `rm -rf /` hâline gelir — seçenek etkinken betik o satıra hiç
ulaşmaz.

Seçenek, öntanımlı değer gösterimlerini kullanmayı zorunlu kılar:

```sh
#!/usr/bin/env bash
set -u
echo "kutuk: ${KUTUK:-erisim.log}"
echo "ilk arguman: ${1-yok}"
echo "arguman sayisi: $#"
```

```
kutuk: erisim.log
ilk arguman: yok
arguman sayisi: 0
```

Değişkenler ve Tırnak Kuralları dersinde tanıtılan `${v-yedek}` ve `${v:-yedek}`
gösterimleri burada zorunluluğa dönüşür: verilmemiş bir konumsal parametre okunacaksa
öntanımlı değer yazılmalıdır. Bu, betiğin arayüzünü belgeleyen bir yan etki de üretir.

#### Boru Hattı Kodu

`set -o pipefail`, boru hattının kodunu sıfırdan farklı kod döndüren en sağdaki komutun
koduna eşitler. Boru Hattı dersinde tanıtılmıştı; katı kipin ayrılmaz parçasıdır.

```sh
#!/usr/bin/env bash
# boru.sh
set -eo pipefail
grep '999' erisim.log | wc -l
echo "BU SATIR YAZILMAZ"
```

```
0
```

```sh
bash boru.sh > /dev/null
echo $?
```

```
1
```

`pipefail` olmadan bu betik `0` yazıp başarıyla biterdi: `wc` sorunsuz çalıştı ve hattın
kodu onunkiydi.

`pipefail` ve `set -e` ile birlikte, `SIGPIPE` ile kesilen hatların da başarısız
sayılacağı unutulmamalıdır. `komut | head -5` biçimindeki hatlar bilinçli olarak
bastırılmalıdır.

#### Alan Ayırıcısını Daraltmak

`IFS=$'\n\t'` ataması, kelime bölmeden boşluğu çıkarır: bölme yalnızca satır sonu ve
sekmede yapılır.

```sh
IFS=$'\n\t'
d="a b:c"
printf '<%s>\n' $d
```

```
<a b:c>
```

Tırnaksız genişletme bile bölünmedi. Bu, boşluk içeren dosya adlarının yol açtığı hata
sınıfına karşı ek bir savunmadır.

Uyarı gerekiyor: bu ayar geneldir ve boşluğa göre bölmeye dayanan her kodu etkiler. Bir
değişkeni sözcüklerine ayırmak isteyen bir döngü, ayardan sonra tek tur döner. Katı kipin
diğer üç seçeneği koşulsuz önerilirken bu seçenek, betiğin ne yaptığına bakılarak
kararlaştırılmalıdır.

#### Aritmetik Tuzağı

Aritmetik ve Komut Yerine Koyma dersinde `(( ))` komutunun çıkış kodunun değerin tersi
olduğu gösterilmişti. Katı kip altında bu, betiği durduran bir hataya dönüşür:

```sh
#!/usr/bin/env bash
# sayac.sh
set -e
sayac=0
(( sayac++ ))
echo "BU SATIR YAZILMAZ (sayac=$sayac)"
```

```
```

```sh
bash sayac.sh > /dev/null
echo $?
```

```
1
```

`sayac++` son ek biçimi **eski değeri** — yani sıfırı — döndürdü ve `(( ))` bunu
başarısızlık saydı. Güvenli yazım aritmetik genişletmedir:

```sh
#!/usr/bin/env bash
set -e
sayac=0
sayac=$(( sayac + 1 ))
echo "sayac=$sayac"
```

```
sayac=1
```

Bu tuzak, katı kipin neden dikkatle uygulandığını gösterir: seçenekler betiğin anlamını
değiştirmez, ama hangi durumların "hata" sayılacağını değiştirir ve bazı deyimler bu yeni
tanımla uyumsuzdur.

#### Beklenen Başarısızlıkları Bastırmak

Bazı komutların sıfırdan farklı kod döndürmesi normaldir; süzgeç aracının "eşleşme yok"
bildirimi bunun tipik örneğidir. Katı kip altında bu durumlar açıkça bastırılır.

```sh
#!/usr/bin/env bash
set -euo pipefail
adet=$(grep -c 'YOKBOYLE' erisim.log || true)
echo "eslesme sayisi: $adet"
if grep -q 'YOKBOYLE' erisim.log; then echo "var"; else echo "yok"; fi
```

```
eslesme sayisi: 0
yok
```

İki yöntem kullanıldı. `|| true`, kodu koşulsuz sıfırlar; sayının kendisi anlamlıysa
uygundur. `if` içinde çağırmak ise başarısızlığı zaten koşul olarak yorumlar; varlık
sorusunda doğru biçim budur.

`|| true` ölçülü kullanılmalıdır. Her satıra eklendiğinde katı kip anlamını yitirir ve
betik, hataları yutan eski hâline döner. Kural: **bastırma, o komutun başarısızlığının
neden beklenen olduğunu bilerek yapılır ve gerekiyorsa bir yorumla belgelenir.**

#### Hata Kancası

`trap ... ERR`, erken çıkışı tetikleyen her komut için bir işleyici çalıştırır. Hatanın
nerede oluştuğunu bildirmek, katı kipin ürettiği sessiz çıkışları izlenebilir kılar.

`set -E` seçeneği bu kancanın fonksiyonlar ve alt kabuklar içinde de geçerli olmasını
sağlar; onsuz kanca yalnızca üst düzeyde çalışır.

```sh
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

hata_yakala() {
  local kod=$? satir=$1
  printf 'rapor.sh: %d. satirda hata, cikis kodu %d\n' "$satir" "$kod" >&2
  exit "$kod"
}
trap 'hata_yakala "$LINENO"' ERR

echo "1: baslangic"
grep -q 'YOKBOYLE' erisim.log
echo "2: BU SATIR YAZILMAZ"
```

```
1: baslangic
rapor.sh: 13. satirda hata, cikis kodu 1
```

Kanca gövdesindeki ilk satır `local kod=$?` biçiminde yazılmıştır; `$?` başka bir komut
çalıştırılmadan okunmalıdır, yoksa değeri değişir. `$LINENO`, kancanın **kurulduğu** yerde
değil **tetiklendiği** yerde genişlemesi için tek tırnak içinde bırakılmıştır.

`trap` komutunun kendisi ve sinyal işleme, sonraki dersin konusudur.

#### Katı Kip Başlığı

Bu dersin sonucu, betiklerin başına konan sabit bir başlıktır:

```sh
#!/usr/bin/env bash
set -Eeuo pipefail
```

Dört seçenek dört ayrı hata sınıfını kapatır: başarısız komut, tanımsız değişken, boru
hattında kaybolan kod ve fonksiyon içinde kaybolan hata kancası. Beşincisi — `IFS`
daraltması — betiğe göre kararlaştırılır.

Başlık bir güvence değil, bir taban çizgisidir. `set -e` istisnaları göz önünde
tutulduğunda, kritik komutların kodları hâlâ elle sınanmalıdır. Katı kip, gözden kaçanları
yakalar; gözden kaçmaması gerekenlerin yerini tutmaz.

#### Özet

- `set -e` sıfırdan farklı kod döndüren komuttan sonra kabuğu sonlandırır; koşul
  bağlamlarında, zincir işleçlerinin solunda ve koşulda çağrılan fonksiyonların gövdesinde
  askıya alınır.
- `set -u` tanımsız değişken genişletmesini hata sayar ve öntanımlı değer gösterimlerini
  zorunlu kılar.
- `set -o pipefail` boru hattının kodunu sıfırdan farklı kod döndüren en sağdaki
  komutun koduna bağlar.
- `(( sayac++ ))` sıfır değerinde başarısızlık bildirir; katı kip altında
  `sayac=$(( sayac + 1 ))` yazılır.
- Beklenen başarısızlıklar `|| true` ya da koşul bağlamıyla bilinçli olarak bastırılır.
- `set -E` ile birlikte `trap ... ERR`, hatanın satır numarasını bildiren bir kanca kurar.

#### Sonraki Adım

Katı kip betiği hata anında durduruyor — ama durduğu yerde ne bırakıyor? Yarım yazılmış bir
çıktı dosyası, temizlenmemiş geçici dosyalar, başka bir sürecin beklediği bir kilit.
Sonraki ders sinyalleri ve çıkışta çalışan temizlik kancalarını ele alır; geçici dosyanın
güvenli biçimde yaratılması ve her çıkış yolunda silinmesi de orada kurulacak.

### Tuzaklar ve Temizlik

Source: https://academia.sh/tr/kurslar/kabuk-programlama/tuzaklar-ve-temizlik

Sinyal kavramı, tuzak kurma, çıkışta çalışan temizlik kancası, geçici dosyanın güvenli yaratımı ve eşzamanlı çalışmayı engelleyen kilit.

Katı kip betiği hata anında durduruyor. Durduğu yerde ne bıraktığı ise ayrı bir sorudur:
yarım yazılmış çıktı dosyası, temizlenmemiş geçici dizin, başka bir sürecin beklediği bir
kilit. Betiğin doğru sonlanması, doğru çalışması kadar önemlidir.

Bilgisayarlar Nasıl Çalışır kursunda kesme, donanımın işlemciye "işini böl" demesiydi.
**Sinyal (signal)**, aynı fikrin süreçler düzeyindeki karşılığıdır: çekirdeğin ya da başka
bir sürecin, çalışan bir sürece gönderdiği asenkron bildirim. Bu ders sinyalleri yakalamayı
ve çıkışta yapılacak temizliği kurar.

#### Sinyaller

Bir süreç sinyal aldığında öntanımlı davranışı uygular — çoğu sinyal için bu, sonlanmaktır.
Betiklerde ilgilenilen sinyaller azdır:

| Sinyal | Numara | Kaynak |
|---|---|---|
| `SIGHUP` | 1 | denetleyen terminal kapandı |
| `SIGINT` | 2 | klavyeden kesme |
| `SIGTERM` | 15 | düzgün sonlandırma isteği |
| `SIGKILL` | 9 | koşulsuz sonlandırma |
| `SIGPIPE` | 13 | okuma ucu kapalı boruya yazma |

Çıkış Kodları dersinde sinyalle sonlanan sürecin **128 + sinyal numarası** kodu döndürdüğü
gösterilmişti; `SIGTERM` için 143, `SIGINT` için 130.

`SIGKILL` ve `SIGSTOP` yakalanamaz, yok sayılamaz ve yeniden tanımlanamaz. Bu, işletim
sisteminin süreç üzerinde son sözü söylemesini güvence altına alır. Dolayısıyla hiçbir
temizlik kancası, `SIGKILL` ile sonlandırılan bir betikte çalışmaz — temizliğin **tek**
dayanağı olamaz.

#### Tuzak Kurmak

`trap 'komut' sinyal...` biçimi, sinyal geldiğinde çalışacak komutu belirler. Kabuk
ayrıca dört **sözde sinyal** tanır: `EXIT`, `DEBUG`, `RETURN` ve `ERR`.

`EXIT` en önemlisidir: kabuk hangi yolla sonlanırsa sonlansın — normal bitiş, `exit`
çağrısı, katı kipin erken çıkışı, yakalanan bir sinyalin işleyicisinden sonra — çalışır.

```sh
#!/usr/bin/env bash
set -Eeuo pipefail

gecici=$(mktemp -d)
temizle() { rm -rf "$gecici"; }
trap temizle EXIT

echo "veri" > "$gecici/ara.txt"
echo "gecici dizin icerigi: $(ls "$gecici")"
```

```
gecici dizin icerigi: ara.txt
```

Betik bittiğinde dizin silinmiş olur. Hata yolunda da aynısı geçerlidir:

```sh
#!/usr/bin/env bash
set -Eeuo pipefail
gecici=$(mktemp -d)
trap 'rm -rf "$gecici"' EXIT
echo "islem basladi"
false
echo "BU SATIR YAZILMAZ"
```

```
islem basladi
```

Katı kip `false` satırında betiği durdurdu; `EXIT` tuzağı yine de çalıştı ve geçici dizin
kalmadı.

Tuzak, kurulduğu andan itibaren geçerlidir. Bu yüzden **kaynak yaratılır yaratılmaz**
kurulur; iki satır arasında bir hata oluşursa temizlik yapılmamış olur.

#### Çıkış Kodunu Korumak

`EXIT` tuzağının gövdesi, betiğin çıkış kodunu değiştirmemelidir. Kodun okunması için
gövdenin **ilk** komutu `$?` okumalıdır:

```sh
#!/usr/bin/env bash
# korunan.sh
temizle() { local kod=$?; echo "temizlik, korunan kod=$kod"; }
trap temizle EXIT
exit 7
```

```
temizlik, korunan kod=7
```

```sh
bash korunan.sh > /dev/null
echo $?
```

```
7
```

Tuzak gövdesi kendisi bir `exit` çağırırsa betiğin kodu o olur; çağırmazsa özgün kod
korunur. Temizlik başarısızlığının betiğin sonucunu değiştirmesi istenmiyorsa, gövdedeki
komutların kodları bastırılmalıdır.

#### Sinyali Yeniden Göndermek

Bir sinyali yakalayıp temizlik yapmak, sürecin o sinyalle sonlandığı bilgisini yok eder:
betik normal biterse çıkış kodu 0 olur ve çağıran, sürecin kesildiğini anlayamaz.

Doğru desen üç adımlıdır: temizle, tuzağı kaldır, sinyali kendine yeniden gönder.

```sh
#!/usr/bin/env bash
# sinyal.sh
temizle() { echo "temizlik calisti (kaynak: $1)"; }
trap 'temizle EXIT' EXIT
trap 'temizle TERM; trap - TERM; kill -TERM $$' TERM
echo "hazir"
kill -TERM $$
```

Gerçek bir kullanımda sinyali dışarıdan bir servis yöneticisi ya da `kill` komutu gönderir;
burada betik sinyali kendine gönderir, böylece deney tek başına yinelenebilir:

```
hazir
temizlik calisti (kaynak: TERM)
temizlik calisti (kaynak: EXIT)
```

```sh
bash sinyal.sh > /dev/null
echo $?
```

```
143
```

Kod `143`; yani `128 + 15`. Süreci yöneten bir servis yöneticisi ya da zamanlayıcı, bu
kodu görerek betiğin hata vermediğini, sonlandırıldığını ayırt edebilir.

`trap - SINYAL` yazımı tuzağı kaldırıp öntanımlı davranışı geri getirir. Bu adım atlanırsa
`kill` çağrısı yine kendi tuzağını tetikler ve sonsuz döngü oluşur.

`EXIT` tuzağının da çalıştığına dikkat edin: temizlik iki kez yürüdü. Bu yüzden temizlik
fonksiyonu **yinelemeye dayanıklı (idempotent)** yazılmalıdır — `rm -rf` ve `rmdir` çağrıları
zaten yok olan bir yolu ikinci kez silmeye çalıştığında zarar vermez, ama sayaç artıran ya
da ileti gönderen bir temizlik iki kez çalışırsa yanlış olur.

#### Geçici Dosyaları Güvenle Yaratmak

Geçici dosya adını elle üretmek — `/tmp/rapor.$$` gibi — üç açıdan yanlıştır.

**Öngörülebilir.** Süreç numaraları dar bir aralıktadır ve yeniden kullanılır; bir
saldırgan adı önceden tahmin edip o yola bir sembolik bağ koyabilir. Betik dosyayı
açtığında bağın gösterdiği yere yazar.

**Yarış içerir.** "Dosya var mı" sınaması ile yaratma arasındaki aralık, araya girmeye
açıktır.

**Çakışabilir.** Aynı betiğin iki örneği aynı süreç numarasını farklı zamanlarda alabilir.

`mktemp`, adı öngörülemez biçimde üretir ve dosyayı **atomik olarak** yaratır; ad
çakışırsa yeniden dener. `-d` seçeneği dizin yaratır ve birden çok geçici dosya gerektiğinde
tek bir temizlik noktası sağlar.

```sh
gecici=$(mktemp -d)
trap 'rm -rf "$gecici"' EXIT
```

`mktemp` yarattığı dosya ve dizinleri yalnızca sahibinin erişebileceği izinlerle açar;
elle `touch` edilen bir dosyada bu güvence yoktur.

`rm -rf "$gecici"` satırındaki tırnak zorunludur. Değişken tanımsız kalırsa — `set -u`
olmadan — komut `rm -rf` hâline gelir; tırnak, boşluk içeren yollarda ikinci bir korumadır.
Katı kipin `set -u` seçeneği bu satırın en önemli koruyucusudur.

#### Eşzamanlı Çalışmayı Engellemek

Zamanlanmış bir rapor betiği, önceki çalışma bitmeden yeniden başlatılabilir. İki örnek
aynı geçici dosyalara yazarsa sonuç bozulur.

Kilit almanın taşınabilir yolu dizin yaratmaktır: `mkdir`, hedef zaten varsa başarısız olan
**atomik** bir işlemdir. Dosya yaratmak aynı güvenceyi vermez, çünkü "var mı" sınaması ile
yaratma iki ayrı adımdır.

```sh
#!/usr/bin/env bash
# kilit.sh
set -Eeuo pipefail
KILIT="rapor.kilit"

if ! mkdir "$KILIT" 2>/dev/null; then
  echo "baska bir calisma suruyor, cikiliyor" >&2
  exit 75
fi
trap 'rmdir "$KILIT"' EXIT

echo "kilit alindi, is yapiliyor"
sleep 1
```

Bir örnek arka planda çalışırken ikincisi başlatıldığında (`until` döngüsü ilk örneğin
kilidi aldığını bekler; ikinci çağrıdaki `2>&1`, hata iletisini standart çıktıya katıp
çıktıda sırasıyla görünmesini sağlar):

```sh
bash kilit.sh &
until [ -d rapor.kilit ]; do sleep 0.05; done
bash kilit.sh 2>&1
echo "ikinci calisma kodu=$?"
wait
```

```
kilit alindi, is yapiliyor
baska bir calisma suruyor, cikiliyor
ikinci calisma kodu=75
```

Tuzağın `mkdir` başarılı olduktan **sonra** kurulması zorunludur. Önce kurulsaydı, kilidi
alamayan ikinci örnek çıkarken başkasının kilidini silerdi.

`75` kodu, "geçici hata, sonra yeniden dene" anlamında yaygın bir sözleşmedir; zamanlayıcı
bu kodu gerçek bir hatadan ayırt edebilir.

Bu desenin bilinen zayıflığı, betik `SIGKILL` ile sonlandırıldığında ya da makine
kapandığında kilidin kalmasıdır. Uzun ömürlü sistemlerde kilit dizinine süreç numarası
yazılır ve kilit alınamadığında o sürecin hâlâ yaşayıp yaşamadığı sınanır.

#### Betiğe Uygulama

`rapor.sh` katı kip başlığını ve temizlik altyapısını alır:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
set -Eeuo pipefail
export LC_ALL=C

BETIK_ADI="rapor.sh"
gecici=""

temizle() {
  local kod=$?
  [ -n "$gecici" ] && rm -rf "$gecici"
  return "$kod"
}
trap temizle EXIT
trap 'temizle; trap - INT;  kill -INT  $$' INT
trap 'temizle; trap - TERM; kill -TERM $$' TERM

gecici=$(mktemp -d)
```

`gecici` değişkeninin tuzak kurulmadan **önce** boş dizgiyle ilklendirilmesi, `set -u`
altında gereklidir: tuzak, `mktemp` çağrısından önce bir hata oluşursa da çalışır ve o anda
değişken tanımsız olurdu.

Önceki dersteki `karsilastir` fonksiyonu artık geçici dosya bırakmıyor; `$gecici` altındaki
her şey çıkışta siliniyor.

#### Özet

- Sinyal, çalışan bir sürece gönderilen asenkron bildirimdir; `SIGKILL` ve `SIGSTOP`
  yakalanamaz, bu yüzden temizlik tek dayanak olamaz.
- `trap ... EXIT`, kabuğun hangi yolla sonlandığından bağımsız olarak çalışır ve kaynak
  yaratılır yaratılmaz kurulur.
- `EXIT` tuzağının ilk komutu `$?` okumalıdır; yoksa betiğin çıkış kodu kaybolur.
- Sinyal yakalandığında temizlenip tuzak kaldırılır ve sinyal kendine yeniden gönderilir;
  böylece çıkış kodu `128 + sinyal` olarak korunur.
- Geçici dosya `mktemp` ile yaratılır: ad öngörülemez, yaratım atomiktir ve izinler
  dardır.
- `mkdir` atomik olduğu için taşınabilir kilit mekanizması olarak kullanılır; tuzak
  kilidi aldıktan sonra kurulur.

#### Sonraki Adım

Betik artık düzgün duruyor ve arkasında iz bırakmıyor. Yanlış sonuç üretiyorsa bunu nasıl
bulacağımız ise başka bir soru. Sonraki ders hata ayıklama araçlarını ele alır: her komutu
çalıştırmadan önce yazan izleme kipi, izleme çıktısını okunur kılan biçim değişkeni,
çalıştırmadan yapılan sözdizim denetimi ve izlemenin betiğin yalnızca bir bölümüne
uygulanması.

### Betik Hata Ayıklama

Source: https://academia.sh/tr/kurslar/kabuk-programlama/betik-hata-ayiklama

Sözdizim denetimi, izleme kipi ve istem biçimi, izlemenin yönlendirilmesi, değişken ve çağrı yığını incelemesi.

Betik düzgün duruyor ve arkasında iz bırakmıyor. Yanlış sonuç ürettiğinde bunu nasıl
bulacağımız ayrı bir sorudur.

Kabuk betiklerinde hata ayıklamayı zorlaştıran iki özellik vardır. Birincisi, çalışan
şeyin çoğunlukla kabuğun kendisi değil çağırdığı programlar olması; ikincisi, genişletme
kurallarının sonucu yazılan metinden farklı kılabilmesi. İkinci nokta belirleyicidir:
`rm "$dizin/$ad"` satırının ne sildiğini, kabuğun o satırı nasıl genişlettiğini görmeden
bilemezsiniz.

Bu dersin araçları tam olarak o genişletilmiş hâli görünür kılar.

#### Çalıştırmadan Denetlemek

`bash -n` betiği ayrıştırır ama hiçbir komut çalıştırmaz. Betik Anatomisi dersinde
tanıtılmıştı; yıkıcı işlem içeren betiklerde ilk adımdır.

Sınanan `bozuk.sh`, kapatılmamış bir koşul içerir:

```sh
#!/usr/bin/env bash
# bozuk.sh — kutuk ozeti, sozdizimi hatasi barindirir
kutuk="$1"

if [ -f "$kutuk" ]; then
  echo "satir sayisi: $(wc -l < "$kutuk")"
```

```sh
bash -n bozuk.sh
```

```
bozuk.sh: line 7: syntax error: unexpected end of file
```

Hata iletisi (bash 5.2'de) yalnızca sorunun fark edildiği satırı verir: ayrıştırıcı
dosyanın sonuna varıp hâlâ açık bir yapı bulmuştur. Bash 5.2'de yapının nerede
başladığını betikte kendiniz ararsınız (bash 5.3 iletinin sonuna
``from `if' command on line 5`` gibi bir ek koyarak bu satırı verir); `fi` ile
eşleşmeyen son `if` genellikle aranan yerdir.

`-n` yalnızca sözdizimini görür. Var olmayan komutlar, yanlış seçenekler, tanımsız
değişkenler ve mantık hataları bu denetimden geçer. Bir betiğin `bash -n` ile temiz
çıkması, çalışacağı anlamına gelmez.

#### İzleme Kipi

`set -x` seçeneği, her komutu **genişletmelerden sonra** ve çalıştırmadan önce standart
hataya yazar. Betiğin tamamı için `bash -x betik` biçiminde de açılabilir.

Örnekler, erişim kütüğünde en çok istenen yolları sayan `ayikla.sh` betiği üzerinde
yürür:

```sh
# ayikla.sh — en cok istenen yollari sayar
kutuk=erisim.log
adet=3
en_cok() {
  cut -d' ' -f7 "$1" | sort | uniq -c | sort -k1,1nr | head -"$2"
}
en_cok "$kutuk" "$adet"
```

Betiğin kendi çıktısı `/dev/null`'a atılır; ekrana yalnızca izleme düşer.

```sh
bash -x ayikla.sh 2>&1 > /dev/null | head -3
```

```
+ kutuk=erisim.log
+ adet=3
+ en_cok erisim.log 3
```

Ardından boru hattının beş parçası için birer iz satırı gelir; parçalar aynı anda
başladığından bu satırların sırası koşudan koşuya değişir, bu yüzden burada ve aşağıdaki
`PS4` çıktısında yalnızca ilk üç satır gösterilmiştir.

İki bilgi görülüyor. Değişkenler beklenen değerleri aldı. Fonksiyon `erisim.log` ve `3`
argümanlarıyla çağrıldı. Boru hattı halkaları da ayrı ayrı listelenir; belirli bir
halkayı görmek için çıktı süzülür:

```sh
bash -x ayikla.sh 2>&1 > /dev/null | grep cut
```

```
+ cut '-d ' -f7 erisim.log
```

`cut` satırındaki `'-d '` yazımına dikkat edin: kabuk, tırnaklama gerektiren argümanları
izleme çıktısında tırnak içinde gösterir. Bu, boşluk içeren bir argümanın gerçekten tek
parça geçip geçmediğini gösteren en doğrudan kanıttır.

#### İzleme İstemini Biçimlendirmek

Öntanımlı `+ ` öneki, uzun betiklerde hangi satırın çalıştığını söylemez. `PS4` değişkeni
bu öneki belirler ve her satır yazılmadan önce genişletilir.

```sh
export PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-ana}: '
bash -x ayikla.sh 2>&1 > /dev/null | head -3
```

```
+ ayikla.sh:2:ana: kutuk=erisim.log
+ ayikla.sh:3:ana: adet=3
+ ayikla.sh:7:ana: en_cok erisim.log 3
```

Her satır artık dosya adı, satır numarası ve fonksiyon adı taşıyor. `PS4` ataması **tek
tırnak** içinde yazılmıştır: değişkenler atama anında değil, her izleme satırı yazılırken
genişlemelidir.

Boru hattı halkalarında da fonksiyon adı görünür:

```sh
export PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-ana}: '
bash -x ayikla.sh 2>&1 > /dev/null | grep 'en_cok: cut'
```

```
+ ayikla.sh:5:en_cok: cut '-d ' -f7 erisim.log
```

`FUNCNAME[0]` üst düzeyde tanımsızdır; `:-ana` öntanımlı değeri `set -u` altında da
çalışmasını sağlar.

#### Seçmeli İzleme

Uzun bir betiğin tamamını izlemek, aranan satırı binlerce satırlık çıktının içinde bırakır.
`set -x` ve `set +x` çiftiyle yalnızca ilgilenilen bölüm izlenir.

```sh
#!/usr/bin/env bash
exec 2>&1
echo "izlenmeyen bolum"
set -x
kod=404
sinif="${kod:0:1}"
set +x
echo "sonuc: ${sinif}xx"
```

```
izlenmeyen bolum
+ kod=404
+ sinif=4
+ set +x
sonuc: 4xx
```

Kapanış satırı da izleme çıktısına düşer; kapanışın kendisi çalıştırılan bir komuttur.
İzleme standart hataya gider; örnekteki `exec 2>&1` satırı onu standart çıktıya bağlar ve
izlemenin betiğin kendi çıktısına göre tam yerini gösterir.

#### İzlemeyi Ayırmak

İzleme çıktısı standart hataya gider. Betiğin uyarıları da oraya gittiği için, ikisi
karışır ve betiğin çıktısını kütüğe alan bir zamanlanmış görevde okunmaz hâle gelir.

`BASH_XTRACEFD` değişkeni izlemeyi başka bir tanıtıcıya yönlendirir:

```sh
#!/usr/bin/env bash
exec 9> izleme.log
BASH_XTRACEFD=9
set -x
kod=404
sinif="${kod:0:1}"
set +x
echo "rapor ciktisi: ${sinif}xx"
```

```
rapor ciktisi: 4xx
```

`izleme.log` içeriği:

```
+ kod=404
+ sinif=4
+ set +x
```

Terminale yalnızca raporun kendisi düştü. Yönlendirme dersinde tanıtılan `exec` ile
tanıtıcı açma deseni burada işlevini buluyor: 3 ve üzeri numaralar bu tür kullanımlar için
serbesttir.

#### Ayrıntılı Kip

`set -v`, komutları **genişletmeden önce**, kaynak dosyadaki hâliyle yazar.

```sh
#!/usr/bin/env bash
set -v
kod=404
echo "sinif: ${kod:0:1}xx"
```

```
kod=404
echo "sinif: ${kod:0:1}xx"
sinif: 4xx
```

`-x` genişletilmiş hâli, `-v` yazılmış hâli gösterir. İkisi birlikte açıldığında bir
genişletmenin girdisi ve çıktısı yan yana görülür; genişletme kurallarının yanlış
anlaşıldığı durumlarda en doğrudan tanı yoludur.

#### Her Komuttan Önce Çalışan Kanca

`trap ... DEBUG` her basit komuttan önce çalışır. `BASH_COMMAND` değişkeni, çalıştırılmak
üzere olan komutun metnini tutar.

```sh
#!/usr/bin/env bash
trap 'printf "[%s] %s\n" "$LINENO" "$BASH_COMMAND" >&2' DEBUG
kod=404
sinif="${kod:0:1}"
echo "sonuc: ${sinif}xx"
```

```
[3] kod=404
[4] sinif="${kod:0:1}"
[5] echo "sonuc: ${sinif}xx"
sonuc: 4xx
```

`DEBUG` kancası, `set -x` ile elde edilemeyen bir şey sağlar: kancanın gövdesi rasgele
kod olabilir. Belirli bir değişken beklenmedik bir değere geçtiğinde durmak ya da her
komuttan önce bir değişkenin değerini yazmak bu yolla yapılır.

Bedeli maliyettir: kanca her komut için çalışır. Üretim betiklerinde bırakılmaz.

#### Değişkenleri İncelemek

`declare -p`, bir değişkenin tipini ve içeriğini yeniden kullanılabilir biçimde yazar.
Diziler için `echo` yeterli değildir; `declare -p` eleman sınırlarını gösterir.

```sh
kodlar=(200 404 500)
declare -A sayim=([404]=4 [500]=3)
declare -p kodlar sayim
```

```
declare -a kodlar=([0]="200" [1]="404" [2]="500")
declare -A sayim=([404]="4" [500]="3" )
```

`-a` dizinli, `-A` ilişkisel diziyi gösteriyor. Seyrek bir dizide dizinlerin atladığı,
boş bir elemanın gerçekten boş olduğu ancak bu çıktıda görülür.

#### Çağrı Yığınını Okumak

Bash, çağrı zincirini üç koşut dizide tutar: `FUNCNAME` fonksiyon adlarını, `BASH_SOURCE`
dosya adlarını, `BASH_LINENO` çağrı satırlarını. Bir hata işleyicisinde bunları yazmak,
hatanın nereden geldiğini gösterir.

```sh
# yigin.sh
nerede() {
  local i
  for (( i = 1; i < ${#FUNCNAME[@]}; i++ )); do
    printf '  %s() <- %s:%s\n' "${FUNCNAME[i]}" "${BASH_SOURCE[i]}" "${BASH_LINENO[i-1]}"
  done
}
ic()  { nerede; }
dis() { ic; }
dis
```

```
  ic() <- yigin.sh:8
  dis() <- yigin.sh:9
  main() <- yigin.sh:10
```

Zincir içten dışa okunur: `nerede` çağrısı `ic` içinden geldi, `ic` çağrısı `dis`
içinden, `dis` çağrısı üst düzeyden. Dizin kaydırması (`BASH_LINENO[i-1]`) bilinçlidir;
üç dizi aynı olayın farklı yönlerini farklı hizalamalarla tutar.

Bu fonksiyon, önceki derste kurulan `ERR` kancasına eklendiğinde betik, hatanın yalnızca
satırını değil çağrı yolunu da bildirir.

#### Bir Yöntem

Araçların sırası, hata ayıklamanın yöntemini de verir:

1. `bash -n` ile sözdizimini geçirin.
2. Katı kip başlığını ekleyin; hataların çoğu kendiliğinden görünür hâle gelir.
3. Sorunun hangi bölümde olduğunu daraltın — bölümün sonuna geçici bir `exit` koyup
   ikiye bölmek en hızlı yoldur.
4. Daraltılan bölümü `set -x` ile izleyin ve `PS4` ile konum bilgisi ekleyin.
5. Genişletmenin girdisi ile çıktısı arasında fark arıyorsanız `-v` ile birlikte açın.
6. Karmaşık veri yapılarını `declare -p` ile yazdırın.

Bu sıra, en ucuz aracı en başa koyar. `set -x` ile başlanan bir oturum, `bash -n`'in bir
saniyede bulacağı bir sözdizim hatasını aramakla geçebilir.

#### Özet

- `bash -n` yalnızca sözdizimini denetler; çalıştırmaz ve mantık hatalarını görmez.
- `set -x` her komutu genişletmelerden **sonra** yazar; tırnaklamanın doğru çalışıp
  çalışmadığını gösteren doğrudan kanıttır.
- `PS4` izleme önekini belirler ve her satır için yeniden genişletilir; tek tırnak içinde
  atanmalıdır.
- `BASH_XTRACEFD` izleme çıktısını ayrı bir tanıtıcıya yollayarak betiğin kendi
  iletilerinden ayırır.
- `set -v` yazıldığı hâli, `set -x` genişletilmiş hâli gösterir; birlikte kullanıldıklarında
  genişletmenin girdisi ve çıktısı karşılaştırılır.
- `declare -p` dizi içeriğini sınırlarıyla birlikte, `FUNCNAME`/`BASH_SOURCE`/`BASH_LINENO`
  çağrı yığınını verir.

#### Sonraki Adım

Bu dersin araçları hatayı, ortaya çıktıktan sonra bulur. Kabuk betiklerindeki hataların
büyük bölümü ise ortaya çıkmadan önce belirlidir: tırnaklanmamış genişletme, sınanmamış
çıkış kodu, taşınabilir olmayan seçenek. Sonraki ders bu sınıfı çalıştırmadan yakalayan
statik çözümleme aracını ele alır ve `rapor.sh` betiğini onun denetiminden geçirir.

### Statik Çözümleme

Source: https://academia.sh/tr/kurslar/kabuk-programlama/statik-cozumleme

Çalıştırmadan hata bulma, yaygın kabuk hata sınıfları, yorumlayıcı bildirimine göre taşınabilirlik denetimi ve uyarı bastırmanın doğru kullanımı.

Önceki dersin araçları hatayı ortaya çıktıktan sonra bulur. Kabuk betiklerindeki hataların
büyük bölümü ise ortaya çıkmadan önce belirlidir: tırnaklanmamış genişletme, sınanmamış
çıkış kodu, dosya listesini komut çıktısından üretme, yorumlayıcı bildiriminin izin
vermediği bir özelliğin kullanılması.

Bu hatalar kodun kendisine bakılarak, çalıştırılmadan görülebilir. **Statik çözümleme
(static analysis)**, kaynak metni ayrıştırıp bilinen hatalı desenleri arayan yöntemdir.
Kabuk için bu iş, betiği ayrıştırıp yüzlerce desen kuralını uygulayan bir araca devredilir.

Aracın değeri, bu kursta tek tek anlatılan tuzakların hepsini birden hatırlamasıdır.

#### Bir Denetim

Aşağıdaki betik, önceki derslerde ele alınan hataların çoğunu bir arada taşıyor:

```sh
#!/bin/sh
# kusurlu.sh
kutuk=$1
for f in $(ls kutukler); do
  echo "islenen: $f"
done
if [ $kutuk = "erisim.log" ]; then
  adet=`wc -l < $kutuk`
  echo "satir: $adet"
fi
cd /tmp
rm -rf $kutuk_yedek
```

Çözümleyicinin uzun biçimli çıktısı, bulguyu kaynak satırın altında işaretler ve
düzeltilmiş yazımı önerir. Aynı bulgular tek satırlık biçimde daha derli toplu okunur:

```sh
shellcheck -f gcc kusurlu.sh
```

```
kusurlu.sh:4:10: error: Iterating over ls output is fragile. Use globs. [SC2045]
kusurlu.sh:7:6: note: Double quote to prevent globbing and word splitting. [SC2086]
kusurlu.sh:8:8: note: Use $(...) notation instead of legacy backticks `...`. [SC2006]
kusurlu.sh:8:17: note: Double quote to prevent globbing and word splitting. [SC2086]
kusurlu.sh:11:1: warning: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. [SC2164]
kusurlu.sh:12:8: warning: kutuk_yedek is referenced but not assigned. [SC2154]
kusurlu.sh:12:8: note: Double quote to prevent globbing and word splitting. [SC2086]
```

Beş ayrı hata sınıfı, on iki satırlık bir betikte. Sonuncusunun sonucu, `set -u` olmayan
bir betikte `rm -rf` komutunun argümansız çalışmasıdır.

Öntanımlı biçim daha ayrıntılıdır: düzeltmesi kayıtlı bulgularda satırın altına `Did you
mean:` başlığıyla düzeltilmiş satırı da ekler; her bulgu bunu almaz. Tek bir bulguyla
uğraşırken bu biçim, toplu denetimde ise yukarıdaki tek satırlık biçim uygundur.

#### Yakalanan Hata Sınıfları

Uyarılar dört öbeğe ayrılır ve her biri bu kursta ayrı bir dersin konusuydu.

**Genişletme ve tırnaklama.** Tırnaklanmamış değişken genişletmesi, `"$@"` yerine `$*`
kullanımı, dizi genişletmesinin yanlış biçimi. Değişkenler ve Tırnak Kuralları dersinin
tamamı bu sınıfa aittir. En sık görülen uyarı budur.

**Çıkış kodu.** Sınanmayan `cd`, sınanmayan komut yerine koyma, `local` ile birlikte atama
yapılması, boru hattı kodunun kaybolması. Hata Yönetimi dersindeki tuzaklar burada
yakalanır.

**Kabuk deyimleri.** Dosya listesini komut çıktısından üretme, `read` çağrısında `-r`
eksikliği, eski ters tırnak yazımı, `[ ]` içinde yanlış işleç. Döngüler ve Koşullar
derslerinin uyarıları.

**Tanımsız ad.** Hiç atanmamış bir değişkenin okunması, yazım yanlışıyla farklı bir ada
dönüşen değişkenler. Bu sınıf, yorumlanan bir dilde çalışma anında bile fark edilmeyebilir.

Her uyarının bir kimliği vardır (`SC` ve dört rakam). Kimlik iki işe yarar: uyarının
ayrıntılı açıklamasına ulaşmak ve — gerektiğinde — o uyarıyı hedefli biçimde bastırmak.

#### Şiddet Düzeyleri

Uyarılar dört düzeye ayrılır: `error`, `warning`, `info`, `style`. `-S` seçeneği belirli
bir düzeyin altındakileri gizler:

```sh
shellcheck -S warning -f gcc kusurlu.sh
```

```
kusurlu.sh:4:10: error: Iterating over ls output is fragile. Use globs. [SC2045]
kusurlu.sh:11:1: warning: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. [SC2164]
kusurlu.sh:12:8: warning: kutuk_yedek is referenced but not assigned. [SC2154]
```

Üç bulgu kaldı; `info` ve `style` düzeyindeki tırnaklama ve ters tırnak uyarıları
gizlendi.

Bu, var olan bir kod tabanına çözümleme eklerken yararlıdır: önce `error` düzeyi
temizlenir, sonra eşik kademeli olarak düşürülür. Eşiği kalıcı olarak yüksek tutmak ise
aracın değerinin çoğunu bırakmak demektir — `info` düzeyindeki tırnaklama uyarıları en sık
gerçek hataya dönüşenlerdir.

#### Taşınabilirlik Denetimi

Çözümleyici, yorumlayıcı bildirimini okur ve kuralları ona göre uygular. `#!/bin/sh`
bildiren bir betikte bash eklentileri hata sayılır:

```sh
#!/bin/sh
# tasinabilir.sh
kodlar=(200 404)
if [[ ${#kodlar[@]} -gt 1 ]]; then
  echo "birden cok kod"
fi
```

```sh
shellcheck tasinabilir.sh
```

```

In tasinabilir.sh line 3:
kodlar=(200 404)
       ^-------^ SC3030 (warning): In POSIX sh, arrays are undefined.


In tasinabilir.sh line 4:
if [[ ${#kodlar[@]} -gt 1 ]]; then
   ^-----------------------^ SC3010 (warning): In POSIX sh, [[ ]] is undefined.

For more information:
  https://www.shellcheck.net/wiki/SC3010 -- In POSIX sh, [[ ]] is undefined.
  https://www.shellcheck.net/wiki/SC3030 -- In POSIX sh, arrays are undefined.
```

Bu, Betik Anatomisi dersindeki kuralın araçla uygulanmasıdır: POSIX bildirip bash özelliği
kullanmak, betiğin başka bir sistemde sessizce bozulmasına yol açar. Aynı betik bash
bildirdiğinde bu iki uyarı üretilmez.

Denetimin sınırı da buradan görülür: araç, bildirilen kabuğa göre karar verir. Bildirim
yanlışsa denetim de yanlış yapılır. Bu yüzden yorumlayıcı bildirimi, betiğin gerçekten
kullandığı özelliklere göre yazılmalıdır.

#### Uyarı Bastırma

Bir uyarının bilinçli olarak yok sayılması gerektiğinde, kaynak koda bir yönerge yazılır:

```sh
#!/usr/bin/env bash
# shellcheck disable=SC2086
kutuk="a b.log"
echo $kutuk
```

Yönerge, kendisinden **sonraki** komuta uygulanır; dosyanın başına, ilk komuttan önce
konursa tüm dosyayı kapsar.

Bastırma üç kurala bağlıdır.

**Kimlik verilir.** `disable` yönergesi kimliksiz yazılabilir ama o zaman tüm uyarıları
kapatır ve gelecekte oluşacak gerçek hataları da gizler.

**Kapsam daraltılır.** Dosya düzeyinde bastırma, o dosyadaki her satırı denetimden
çıkarır. Uyarının doğduğu satırın hemen üstüne yazmak yeğlenir.

**Gerekçe yazılır.** Bastırılan uyarının neden yanlış alarm olduğu bir yorumla belirtilir.
Gerekçesiz bir bastırma, kodu okuyan bir sonraki kişi için "burada ne olduğunu bilmiyorum"
demektir.

Kelime bölmenin gerçekten istendiği durumlarda daha iyi bir çözüm vardır: değeri bir diziye
almak. `"${dizi[@]}"` yazımı hem uyarı üretmez hem de niyeti kodda görünür kılar. Bastırma,
yeniden yazmanın mümkün olmadığı hâllere bırakılır.

#### Betiği Denetimden Geçirmek

`rapor.sh` bu kurs boyunca, çözümleyicinin uyaracağı desenlerden kaçınılarak yazıldı.
Aşağıdaki küçük betik aynı kuralları izler:

```sh
#!/usr/bin/env bash
# temiz.sh
set -Eeuo pipefail
kutuk="${1:-erisim.log}"
[ -r "$kutuk" ] || { echo "okunamiyor: $kutuk" >&2; exit 3; }
adet=$(grep -c '' "$kutuk")
printf 'satir: %d\n' "$adet"
```

```sh
shellcheck temiz.sh; echo "kod=$?"
```

```
kod=0
```

Çıkış kodu `0` ve hiçbir çıktı yok. Araç, bulgu varsa sıfırdan farklı kod döndürür; bu,
denetimi bir doğrulama adımına dönüştürmeyi mümkün kılar:

```sh
shellcheck temiz.sh && bash -n temiz.sh && echo "denetim gecti"
```

```
denetim gecti
```

Aynı zincir kursun `rapor.sh` betiği için de kullanılır.

Sürüm denetim sistemine gönderim öncesinde ya da bir sürekli tümleştirme adımında bu
komutun çalıştırılması, kabuk betiklerinin kalitesini korumanın en ucuz yoludur.

#### Statik Çözümlemenin Sınırı

Araç, kodun **anlamını** değil **biçimini** görür. Yakalayamadıkları:

- Yanlış alan numarası (`$7` yerine `$8` yazmak).
- Yanlış düzenli ifade (kalıp geçerli ama beklenen kümeyi betimlemiyor).
- Yanlış iş mantığı (yüzdenin yanlış toplama bölünmesi).
- Çalışma ortamına bağlı hatalar (var olmayan komut, yetersiz izin).

Bu sınıflar için sınama gerekir: betiği bilinen bir girdiyle çalıştırıp çıktısını beklenen
çıktıyla karşılaştırmak. Bu kursun `erisim.log` dosyasının sabit ve yeniden üretilebilir
olması, tam olarak böyle bir sınamayı mümkün kılmak içindir.

Statik çözümleme sınamanın yerini tutmaz; sınamanın bulacağı hata sayısını azaltır.

#### Özet

- Statik çözümleme, betiği çalıştırmadan ayrıştırıp bilinen hatalı desenleri arar.
- Yakalanan başlıca sınıflar tırnaklama, çıkış kodu, kabuk deyimleri ve tanımsız adlardır;
  hepsi bu kursta ayrı derslerin konusuydu.
- Denetim, yorumlayıcı bildirimine göre yapılır; POSIX bildiren betikte bash eklentileri
  uyarı üretir.
- Bastırma kimlikle, dar kapsamla ve gerekçeyle yapılır; kelime bölme isteniyorsa doğru
  çözüm dizi kullanmaktır.
- Araç bulgu varsa sıfırdan farklı kod döndürür; bu, denetimi bir doğrulama adımına
  çevirir.
- Statik çözümleme biçimi görür, anlamı görmez; yanlış alan numarası ve yanlış iş mantığı
  ancak sınamayla bulunur.

#### Sonraki Adım

Betik doğru, sağlam ve denetlenmiş durumda — ama hâlâ elle çalıştırılıyor. Bir raporun
değeri düzenli üretilmesindedir. Son ders zamanlanmış görevleri ele alır: süreli görev
tanımının okunması, zamanlayıcı ortamının etkileşimli kabuktan hangi noktalarda ayrıldığı,
çıktının nereye yazılacağı ve üst üste binen çalışmaların engellenmesi.

### Zamanlanmış Görevler

Source: https://academia.sh/tr/kurslar/kabuk-programlama/zamanlanmis-gorevler

Süreli görev tanımının okunması, zamanlayıcı ortamının etkileşimli kabuktan farkı, çıktı yönetimi, üst üste binmeyi engelleyen sarmalayıcı.

Betik doğru, sağlam ve denetlenmiş durumda — ama hâlâ elle çalıştırılıyor. Bir raporun
değeri düzenli üretilmesindedir: her sabah bir önceki günün kütüğünü çözümleyen ve sonucu
bir yere bırakan bir araç, elle çalıştırılan aynı araçtan farklı bir şeydir.

Bu ders, bir betiği zamanlanmış çalışmaya hazırlamayı ele alır. Konu yalnızca zamanlama
tanımı değildir; asıl mesele, betiğin **insan olmayan bir çağırıcı** tarafından, dar bir
ortamda ve gözlemsiz çalıştırılacak olmasıdır.

#### Süreli Görev Tanımı

Unix sistemlerinde süreli görevler bir zamanlayıcı hizmetine tanımlanır. Tanım tablosu,
kullanıcı başına tutulur ve her satırı beş zaman alanı ile bir komuttan oluşur:

```
dakika  saat  ayın günü  ay  haftanın günü  komut
```

| Alan | Aralık |
|---|---|
| dakika | 0–59 |
| saat | 0–23 |
| ayın günü | 1–31 |
| ay | 1–12 |
| haftanın günü | 0–7 (0 ve 7 pazar) |

Her alan dört biçimden birini alır: sabit sayı, `*` (her değer), `a-b` (aralık), `a,b,c`
(liste). Bir aralığın ardına `/n` eklenirse `n` adımlı seçim yapılır.

```
30 6 * * *          her gun 06:30
0 */4 * * *         her dort saatte bir, saat basi
15 2 * * 1          her pazartesi 02:15
0 0 1 * *           her ayin birinci gunu gece yarisi
*/10 * * * *        her on dakikada bir
```

İki alan birlikte kısıt getirir ama **ayın günü ile haftanın günü** ikilisi istisnadır:
ikisi de `*` dışında bir değer alırsa koşullar "ve" ile değil "veya" ile birleşir. `0 0 13 * 5`
tanımı, ayın 13'ünde **ya da** cuma günü çalışır. Bu, tanımların en sık yanlış okunan
noktasıdır.

Tanım tablosu `crontab -e` ile düzenlenir, `crontab -l` ile listelenir. `crontab -r`
tabloyu **onay sormadan siler**; `-e` ile arasında tek harf bulunduğu için, listeleme
alışkanlığı `-l` üzerinden kurulmalıdır.

Bazı zamanlayıcılar `@daily`, `@hourly`, `@reboot` gibi kısaltmalar tanır. Bunlar POSIX'te
tanımlı değildir; taşınabilir tanımlarda beş alanlı biçim kullanılır.

Zamanlayıcı hizmetinin bir başka sınıfı da servis yöneticisinin zamanlayıcı birimleridir.
Bağımlılık tanımlama, kaçırılan çalışmaları telafi etme ve çalışmaların kütüğünü servis
altyapısıyla birleştirme yetenekleri sunar. Servis yöneticisi modeli Sistem Yönetimi
kursunun konusudur.

#### Zamanlayıcı Ortamı

Zamanlanmış görevlerin başarısız olmasının en yaygın nedeni, betikte değil ortamdadır.
Zamanlayıcı, komutu **oturum açma kabuğu olmadan** çalıştırır: kabuk başlangıç dosyaları
okunmaz, kabuk fonksiyonları ve takma adlar yoktur, ortam değişkenlerinin çoğu
tanımsızdır.

En belirgin fark arama yoludur. Aşağıdaki `ortam.sh`, arama yolunu yazar ve `araci`
adlı bir komutun bulunup bulunmadığını sınar:

```sh
#!/usr/bin/env bash
# ortam.sh
echo "PATH=$PATH"
if command -v araci > /dev/null; then
  echo "araci bulundu mu: evet"
else
  echo "araci bulundu mu: hayir"
fi
```

`araci`, kendi `bin` dizinimizde duran küçük bir betiktir. Önce o dizin arama yoluna
eklenmiş bir oturumda (yalnızca son satır gösterilir), sonra dar bir ortamda çalıştırılan
aynı betik:

```sh
mkdir -p bin
printf '#!/bin/sh\necho merhaba\n' > bin/araci
chmod +x bin/araci
PATH="$PWD/bin:$PATH" bash ortam.sh | tail -n 1
env -i PATH=/usr/bin:/bin bash ortam.sh
```

```
araci bulundu mu: evet
PATH=/usr/bin:/bin
araci bulundu mu: hayir
```

Etkileşimli oturumda bulunan bir araç, zamanlanmış çalışmada bulunamayabilir. Sonuç,
"betik elle çalışıyor ama zamanlayıcıda çalışmıyor" biçiminde ortaya çıkar ve zamanlayıcı
sessiz olduğu için fark edilmesi gecikir.

Üç önlem alınır.

**Arama yolunu betikte belirtin.** Betiğin başına `export PATH=...` yazmak, çağırma
ortamından bağımsızlık sağlar. Kullanılan araçların yolları biliniyorsa bu, en sağlam
çözümdür.

**Dosya yollarını saltık yazın.** Zamanlayıcının başlangıç dizini garanti değildir; göreli
yollar farklı bir dizinde çözülür. Betiğin okuduğu ve yazdığı her yol saltık olmalıdır.

**Gereken ortam değişkenlerini betikte tanımlayın.** Yerel ayar, dil ve saat dilimi
değişkenleri etkileşimli oturumdan devralınmaz. Bu kursta `export LC_ALL=C` satırının
betiğin başına konması, bu nedenle de gereklidir: sıralama düzeni, zamanlayıcı ortamında
başka türlü belirsiz kalırdı.

Tanım tablosuna özgü bir ayrıntı daha vardır: komut alanında `%` karakteri satır sonu
anlamına gelir ve kaçırılmadan yazılamaz. Tarih biçimlendirmesi gibi `%` içeren komutlar
doğrudan tabloya yazılmaz — bir sarmalayıcı betiğe konur.

#### Çıktı Nereye Gider

Zamanlayıcı, görevin ürettiği standart çıktı ve standart hatayı toplayıp kullanıcıya
iletmeye çalışır. İletim yapılandırılmamışsa çıktı kaybolur ya da yerel bir posta
kuyruğunda birikir.

Bu davranış iki sonuç doğurur. Birincisi, her çalıştırmada çıktı üreten bir görev sürekli
bildirim üretir; bildirimler bir süre sonra okunmaz olur. İkincisi, hata iletileri de aynı
yolla kaybolur.

Doğru düzen, çıktıyı açıkça yönetmektir:

```
30 6 * * * $HOME/rapor/rapor-gorevi.sh
```

ve yönlendirmeleri betiğin içinde yapmak. Betiğin içinde yapıldığında yollar okunur kalır,
`%` sorunu doğmaz ve yönlendirme mantığı sürüm denetimine girer.

Konunun ilk dersinde kurulan akış ayrımı burada karşılığını bulur: **raporun kendisi bir
dosyaya, tanı iletileri ayrı bir dosyaya** yazılır. İkisi karıştırılırsa raporu okuyan bir
sonraki adım uyarı metnini veri sanar.

#### Sarmalayıcı Betik

Zamanlanmış görev, iş yapan betiği doğrudan çağırmaz; onu bir sarmalayıcıyla sarar.
Sarmalayıcı ortamı kurar, kilidi alır, çıktıyı yönlendirir ve iş betiğini çağırır.

```sh
#!/usr/bin/env bash
# rapor-gorevi.sh
set -Eeuo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export LC_ALL=C

KOK="$HOME/rapor"
KILIT="$KOK/kilit"
KUTUK_DIZINI="$KOK/kutuk"

mkdir -p "$KUTUK_DIZINI"

if ! mkdir "$KILIT" 2>/dev/null; then
  echo "onceki calisma suruyor" >&2
  exit 75
fi
trap 'rmdir "$KILIT"' EXIT

"$KOK/rapor.sh" -n 3 "$KOK/erisim.log" \
  >> "$KUTUK_DIZINI/rapor.out" 2>> "$KUTUK_DIZINI/rapor.err"
```

Beş karar bir arada duruyor.

**Kilit.** Tuzaklar ve Temizlik dersinde kurulan `mkdir` deseni burada gerçek işlevini
buluyor: her on dakikada bir çalışan ve bazen on dakikadan uzun süren bir görev, kilit
olmadan kendi kendine çakışır. `75` kodu "geçici hata" bildirir.

**Ortam.** `PATH` ve `LC_ALL` betiğin içinde sabitlendi; zamanlayıcının verdiği ortama
güvenilmiyor.

**Saltık yollar.** Hiçbir göreli yol yok; başlangıç dizini ne olursa olsun betik aynı
dosyaları bulur.

**Ayrık çıktı.** Rapor `rapor.out`, tanı iletileri `rapor.err` dosyasına ekleniyor.
Yönlendirmeler `>>` biçiminde; her çalıştırma öncekinin üzerine yazmıyor.

**Katı kip.** Sarmalayıcının kendisi de betiktir ve aynı kurallara tabidir.

Kütük dosyalarının sonsuza dek büyümesi ayrı bir sorundur ve dosya döndürme araçlarıyla
çözülür; bu araçlar Sistem Yönetimi kursunda ele alınır.

#### Görevi Sınamak

Zamanlanmış bir görev, tanımlanmadan önce zamanlayıcının koşullarında sınanmalıdır. Sınama
sırası şudur:

1. Sarmalayıcıyı elle çalıştırın; çıktı dosyalarının doğru yerde oluştuğunu doğrulayın.
2. Aynı çağrıyı dar bir ortamda yineleyin: `env -i PATH=/usr/bin:/bin ./rapor-gorevi.sh`.
   Bu, zamanlayıcı ortamına yakın bir benzetimdir.
3. Kilidi sınayın: bir örnek çalışırken ikincisini başlatın ve `75` kodunu doğrulayın.
4. Hata yolunu sınayın: var olmayan bir kütük adı verin ve `rapor.err` dosyasına anlamlı
   bir ileti düştüğünü görün.
5. Tanımı tabloya, gerçek zamanından birkaç dakika sonrasına yazın ve ilk çalışmayı
   gözleyin.

Çıkış kodu bu zincirin son halkasıdır. Görevin başarısız olduğunu bir izleme sistemine
bildiren şey çıkış kodudur; bu yüzden betiğin ürettiği kodlar bilinçli seçilmiş olmalıdır.
Bu kursta kullanılan kod düzeni şöyleydi:

| Kod | Anlamı |
|---|---|
| 0 | rapor üretildi |
| 2 | kullanım hatası (geçersiz seçenek ya da değer) |
| 3 | girdi hatası (kütük yok, okunamıyor) |
| 75 | geçici hata, sonra yeniden dene (kilit alınamadı) |
| 128 + n | sinyalle sonlandırıldı |

#### Tamamlanmış Betik

Kurs boyunca geliştirilen betiğin son hâli:

```sh
#!/usr/bin/env bash
# rapor.sh — erisim kutugunden ozet rapor uretir.
# Kullanim: rapor.sh [-n SAYI] [KUTUK]
set -Eeuo pipefail
export LC_ALL=C

BETIK_ADI="rapor.sh"
SATIR_KALIBI='^[0-9.]+ - - \[[^]]+\] "[A-Z]+ [^ ]+ HTTP/[0-9.]+" [0-9]{3} [0-9]+$'
gecici=""

hata()  { printf '%s: hata: %s\n'  "$BETIK_ADI" "$*" >&2; }
uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; }
oldur() { hata "$1"; exit "${2:-1}"; }
bolum() { printf '\n== %s ==\n' "$*"; }

temizle() {
  local kod=$?
  [ -n "$gecici" ] && rm -rf "$gecici"
  return "$kod"
}
trap temizle EXIT
trap 'temizle; trap - INT;  kill -INT  $$' INT
trap 'temizle; trap - TERM; kill -TERM $$' TERM
trap 'hata "$LINENO. satirda beklenmeyen hata"' ERR

ozet() {
  awk '
    { istek++; bayt += $10; sinif[substr($9, 1, 1)]++ }
    END {
      printf "  istek   : %d\n", istek
      printf "  bayt    : %d\n", bayt
      printf "  ortalama: %.1f bayt\n", (istek ? bayt / istek : 0)
      for (s = 2; s <= 5; s++)
        printf "  %sxx %4d  %%%.1f\n", s, sinif[s], (istek ? sinif[s] * 100 / istek : 0)
    }
  ' "$1"
}

en_cok_yol() {
  cut -d' ' -f7 "$1" \
    | sed -E 's#/[0-9]+#/:id#g' \
    | sort | uniq -c | sort -k1,1nr -k2,2 | head -"$2"
}

hata_yollari() {
  awk '$9 ~ /^[45]/ { n[$7]++ } END { for (y in n) printf "%3d %s\n", n[y], y }' "$1" \
    | sort -k1,1nr -k2,2
}

adet=5
while getopts ':n:' secenek; do
  case "$secenek" in
    n)  adet="$OPTARG" ;;
    :)  oldur "-$OPTARG deger bekliyor" 2 ;;
    \?) oldur "bilinmeyen secenek -$OPTARG" 2 ;;
  esac
done
shift $((OPTIND - 1))

kutuk="${1:-erisim.log}"
[[ "$adet" =~ ^[1-9][0-9]*$ ]] || oldur "-n icin pozitif tam sayi bekleniyor: $adet" 2
[ -f "$kutuk" ] || oldur "duz dosya degil ya da yok: $kutuk" 3
[ -r "$kutuk" ] || oldur "okuma izni yok: $kutuk" 3
[ -s "$kutuk" ] || uyari "kutuk bos: $kutuk"

gecici=$(mktemp -d)
gecerli="$gecici/gecerli.log"
grep -E "$SATIR_KALIBI" "$kutuk" | awk 'NF == 10' > "$gecerli" || true
bozuk=$(( $(grep -c '' "$kutuk") - $(grep -c '' "$gecerli") ))
[ "$bozuk" -eq 0 ] || uyari "$bozuk satir beklenen bicimde degil, cozumleme disi"

bolum "Kutuk: $kutuk"
ozet "$gecerli"

bolum "En cok istenen $adet yol"
en_cok_yol "$gecerli" "$adet"

bolum "Hata donduren yollar"
hata_yollari "$gecerli"
```

Betiğin son eklentisi, çözümlemeden önce geçerli satırları ayıran adımdır. Düzenli
İfadeler dersindeki satır kalıbı ile Alan Tabanlı İşleme dersindeki alan sayısı sınaması
birlikte uygulanıyor: ikisi de tek başına yetersizdi. Elenen satırlar sayılıp standart
hataya bildiriliyor, sayımlar ise yalnızca geçerli satırlar üzerinden yapılıyor — böylece
bozuk bir girdi raporu sessizce çarpıtmıyor.

Temiz kütükle:

```sh
chmod +x rapor.sh
./rapor.sh -n 3
```

```

== Kutuk: erisim.log ==
  istek   : 30
  bayt    : 117780
  ortalama: 3926.0 bayt
  2xx   18  %60.0
  3xx    3  %10.0
  4xx    6  %20.0
  5xx    3  %10.0

== En cok istenen 3 yol ==
      6 /api/veri
      6 /urun/:id
      4 /index.html

== Hata donduren yollar ==
  4 /yok.html
  3 /api/veri
  2 /gizli
```

İki bozuk satır eklenmiş bir kütükle çıktının ilk yedi satırı gösterilir; `2>&1`
uyarıyı da aynı akışa katar, `sed -n '1,7p'` ise girdinin tamamını okuduğu için boruyu
erken kapatmaz (`head -7` bu güvenceyi vermezdi):

```sh
cp erisim.log tumu.log
printf 'bozuk satir\nbu da bozuk\n' >> tumu.log
./rapor.sh -n 2 tumu.log 2>&1 | sed -n '1,7p'
```

```
rapor.sh: uyari: 2 satir beklenen bicimde degil, cozumleme disi

== Kutuk: tumu.log ==
  istek   : 30
  bayt    : 117780
  ortalama: 3926.0 bayt
  2xx   18  %60.0
```

Uyarı standart hataya, rapor standart çıktıya gitti (burada `2>&1` ile tek akışta
görünüyorlar) ve sayımlar bozuk satırlardan etkilenmedi.

Sarmalayıcı da bu betikle birlikte sınanabilir. Aşağıdaki komutlar `rapor.sh` ve
`erisim.log` dosyalarını `$HOME/rapor` dizinine koyar, sarmalayıcıyı çalıştırır ve
oluşan çıktı dosyalarını listeler:

```sh
mkdir -p "$HOME/rapor"
cp rapor.sh erisim.log "$HOME/rapor/"
bash rapor-gorevi.sh
ls "$HOME/rapor/kutuk"
```

```
rapor.err
rapor.out
```

Betiğin bilinen bir davranışı vardır: çıktısı `head` gibi erken kapanan bir komuta
bağlandığında, `pipefail` etkin olduğu için `ERR` kancası `SIGPIPE` nedeniyle tetiklenir.
Bu, katı kipin bilinçli bir ödünüdür; çıktıyı kısaltmak `-n` seçeneğiyle yapılmalıdır.

#### Özet

- Süreli görev tanımı beş zaman alanı ve bir komuttan oluşur; ayın günü ile haftanın günü
  birlikte verildiğinde koşullar "veya" ile birleşir.
- Zamanlayıcı, komutu oturum açma kabuğu olmadan çalıştırır; arama yolu dardır, başlangıç
  dosyaları okunmaz ve ortam değişkenlerinin çoğu tanımsızdır.
- Betikte arama yolu ve yerel ayar sabitlenir, tüm dosya yolları saltık yazılır.
- Çıktı açıkça yönetilir: rapor ve tanı iletileri ayrı dosyalara eklenir.
- Sarmalayıcı betik ortamı kurar, kilidi alır ve çıktıyı yönlendirir; kilit üst üste binen
  çalışmaları engeller.
- Çıkış kodları izleme sisteminin tek girdisidir; kullanım, girdi ve geçici hata sınıfları
  ayrı kodlarla bildirilir.

#### Kurs Kapanışı

Bu kurs tek bir soruyla başladı: bir komut yazdığı veriyi tam olarak nereye yazar? Yanıtı
üç akış ve dosya tanıtıcısıydı. Oradan çıkan her şey, aynı fikrin genişletilmesidir.

**Akışlar ve boru hatları** komutları birbirine bağlamayı kurdu: yönlendirmenin komuttan
önce uygulanması, boru hattındaki süreçlerin eşzamanlı çalışması, alt kabukta kaybolan
değişkenler ve çıkış kodu sözleşmesi.

**Betik yapısı** tek satırlık hatları bir programa dönüştürdü. Bu bölümün merkezinde,
kabuğu diğer dillerden ayıran şey duruyordu: genişletmenin sonucu yeniden çözümlenir.
Kelime bölme, yol adı genişletmesi ve alıntılama kuralları bu kursun akademik çekirdeğidir;
kabuk betiklerindeki hataların çoğu buradan doğar.

**Metin işleme** işi asıl araçlarına devretti: kalıp dili, satır süzme, akış editörü, alan
tabanlı işleme ve sıralama ailesi. Bölümü kapatan seçim ölçütü — en dar aracı seç, üç
araçtan fazlasını gerektiren problemde aracı değiştir — bu kursun pratik mirasıdır.

**Sağlam betik yazımı** betiği gözlemsiz çalışmaya hazırladı: katı kip, temizlik kancaları,
izleme araçları, statik çözümleme ve zamanlanmış çalışma.

Kurs boyunca aynı betik geliştirildi. `rapor.sh` sabit bir boru hattı olarak başladı;
değişken kazandı, argüman arayüzü edindi, girdilerini sınamayı öğrendi, fonksiyonlara
ayrıldı, sayımlarını tek geçişe indirdi, hatalarda durmayı ve arkasını toplamayı öğrendi.
Son hâli, denetimden geçen ve zamanlayıcıya verilebilen bir araçtır.

Kabuğun sınırı da bu yolculukta göründü. Kabuk, süreçleri birbirine bağlamakta ve dosya
sistemiyle çalışmakta rakipsizdir. Karmaşık veri yapıları, ondalıklı hesap, yapılandırılmış
veri biçimleri ve kapsamlı hata yönetimi için tasarlanmamıştır. İyi bir kabuk betiği,
kendi sınırını bilen betiktir.

Sonraki kurs — **Sistem Yönetimi** — bu betiğin çalıştığı sistemi konu alır. `rapor.sh`
bir zamanlayıcıya verildi; peki o zamanlayıcının kendisi bir servis olarak nasıl tanımlanır
ve denetlenir? Betik bir süreç yarattı; süreçler nasıl izlenir, önceliklendirilir ve
sinyallerle yönetilir? Kütük dosyaları büyüyor; disk ve dosya sistemi yapılandırması nasıl
değiştirilir? Bu kursta sinyaller, çıkış kodları ve süreçler bir betiğin içinden görüldü.
Sonraki kurs onlara sistemin dışından bakar.

