Neden terminal?
Anadolu Market'in gece 02.00'de çalışan stok raporu bir sunucuda, kimsenin ekranına bakmadığı bir makinede koşar. Orada VS Code yok, "Çalıştır" düğmesi yok; yalnızca terminal (terminal, komut satırı, shell) var. Veri ekipleri betiklerini terminalden çalıştırır, zamanlayıcılar (cron, Windows Görev Zamanlayıcı) betiği terminal komutuyla başlatır, CI sistemleri testleri terminalde koşturur. Terminali rahat kullanmak, "kendi bilgisayarımda çalışıyor" ile "şirkette her gece çalışıyor" arasındaki köprüdür.
Bu derste terminalin dört temel fikrini öğreneceksin: neredesin (çalışma dizini), neyi çalıştırıyorsun (komut ve argümanlar), sonuç ne oldu (çıkış kodu) ve veri nereye akıyor (standart girdi/çıktı/hata). Python tarafında bunların karşılığı os.getcwd(), sys.argv, sys.exit(), sys.stdin/stdout/stderr ve os.environ'dur. Kurulum dersinde terminali açıp python --version yazmıştın; şimdi derine iniyoruz.
Neredesin? Çalışma dizini
Terminal her an bir klasörün "içinde" durur; buna çalışma dizini (working directory, cwd) denir. Yazdığın göreli dosya adları bu klasöre göre çözülür. Nerede olduğunu pwd (macOS/Linux) ya da cd (Windows'ta tek başına) söyler; başka klasöre cd klasor ile geçersin.
$ pwd /home/ayse $ mkdir anadolu-rapor $ cd anadolu-rapor $ ls $ cd .. $ pwd /home/ayse
Python içinden çalışma dizinini os.getcwd() ya da pathlib.Path.cwd() verir. Tarayıcıdaki Python da sanal bir dosya sisteminde çalışır; aşağıdaki kodu çalıştırıp kendi "konumunu" gör:
import os
from pathlib import Path
print("Çalışma dizini:", os.getcwd())
print("Path ile:", Path.cwd())
os.makedirs("rapor_deneme", exist_ok=True) # mkdir'in Python karşılığı
os.chdir("rapor_deneme") # cd'nin Python karşılığı
print("Şimdi:", os.getcwd())
os.chdir("..")
print("Geri döndük:", os.getcwd())
Windows ve macOS/Linux komutları
macOS ve Linux aynı aileden (Unix) gelir, komutları neredeyse aynıdır. Windows'ta iki terminal vardır: eski cmd ve modern PowerShell. PowerShell, ls, pwd, cat gibi Unix adlarını da takma ad (alias) olarak tanır; bu yüzden yeni başlayanlara PowerShell'i öneririz.
| Ne yapar | macOS / Linux | Windows cmd | PowerShell |
|---|---|---|---|
| Bulunduğun klasör | pwd | cd | pwd / Get-Location |
| Klasör içeriği | ls, ls -la | dir | ls / dir |
| Klasöre gir / üst klasör | cd veri / cd .. | cd veri / cd .. | aynı |
| Klasör oluştur | mkdir rapor | mkdir rapor | mkdir rapor |
| Dosya içeriğini göster | cat satis.csv | type satis.csv | cat / Get-Content |
| Kopyala / taşı / sil | cp / mv / rm | copy / move / del | cp / mv / rm |
| Ekranı temizle | clear | cls | cls / clear |
| Metin içinde ara | grep HATA log.txt | findstr HATA log.txt | Select-String HATA log.txt |
| Python'ı çağır | python3 | py ya da python | py ya da python |
| Ortam değişkeni oku | echo $HOME | echo %USERPROFILE% | $env:USERPROFILE |
| Son çıkış kodu | echo $? | echo %ERRORLEVEL% | $LASTEXITCODE |
Göreli ve mutlak yol
Mutlak yol (absolute path) kökten başlar: /home/ayse/veri/satis.csv ya da C:\Users\ayse\veri\satis.csv. Nerede olursan ol aynı dosyayı gösterir. Göreli yol (relative path) çalışma dizinine göre yorumlanır: veri/satis.csv, ./satis.csv (bu klasör), ../arsiv/eski.csv (bir üst klasör). ~ ise macOS/Linux'ta ev klasörünün kısaltmasıdır.
import posixpath
from pathlib import PurePosixPath
cwd = PurePosixPath("/home/ayse/anadolu-rapor")
goreli = "../veri/./satis.csv"
birlesik = cwd / goreli
print("Birleştirilmiş:", birlesik)
print("Sadeleştirilmiş:", posixpath.normpath(str(birlesik)))
print("Mutlak mı?", PurePosixPath(goreli).is_absolute(), PurePosixPath("/etc/hosts").is_absolute())
print("Dosya adı:", birlesik.name, "| uzantı:", birlesik.suffix, "| gövde:", birlesik.stem)
PurePosixPath diske dokunmadan yalnızca yol metniyle çalışır; bu yüzden her bilgisayarda aynı sonucu verir ve test yazmak için idealdir. / operatörü yolları birleştirir. .. parçalarını sadeleştirmek için posixpath.normpath kullanılır (gerçek diskte Path.resolve() aynı işi yapar).
open("veri/satis.csv") diyorsa dosya, betiğin klasörüne göre değil, terminalin bulunduğu klasöre göre aranır. cd yapmadan başka yerden çalıştırırsan FileNotFoundError alırsın. Sağlam çözüm: Path(__file__).parent / "veri" / "satis.csv".Python'ı terminalden çalıştırmak
python rapor.py # betiği çalıştır (macOS/Linux'ta çoğu zaman python3) py rapor.py # Windows'ta Python başlatıcısı python -m pip install rich # pip'i "bu Python'ın" modülü olarak çalıştır python -m venv .venv # sanal ortam oluştur python -m json.tool veri.json # JSON'u güzel biçimde yazdır python -c "print(2 ** 10)" # tek satırlık kod
python betik.py bir dosyayı, python -m modul ise Python'ın bulduğu bir modülü betik gibi çalıştırır. pip yerine python -m pip yazmak, paketin tam olarak o Python'a kurulmasını garanti eder; bilgisayarda birden fazla Python varken en sık karışıklığı böyle önlersin.
Betiğe argüman vermek: sys.argv
Terminalde betik adından sonra yazdığın her kelime argüman (argument) olur ve sys.argv listesine metin (str) olarak düşer. İlk eleman (sys.argv[0]) her zaman betiğin adıdır.
$ python rapor.py İzmir 7 --detay # sys.argv == ["rapor.py", "İzmir", "7", "--detay"]
Tarayıcıda terminal olmadığı için aynı mantığı bir liste ile taklit ediyoruz. Profesyonel kodda da yaygın kalıp budur: argümanları main(argv) fonksiyonuna parametre olarak vermek; böylece fonksiyonu test edebilirsin.
def main(argv):
if len(argv) < 2:
print("Kullanım: python rapor.py <şehir> [gün]")
return 2
sehir = argv[1]
gun = int(argv[2]) if len(argv) > 2 else 7 # argümanlar hep str!
print(f"{sehir} için son {gun} günün raporu hazırlanıyor")
return 0
print("Kod:", main(["rapor.py", "İzmir", "30"]))
print("Kod:", main(["rapor.py", "Ankara"]))
print("Kod:", main(["rapor.py"]))
"7" ile 7 farklıdır. Sayı bekliyorsan int() ile dönüştür. Çok seçenekli araçlar için standart kütüphanedeki argparse modülünü orta seviyede göreceksin; sys.argv onun altında yatan temeldir.Çıkış kodu: işin sonucu bir sayı
Her program bittiğinde işletim sistemine bir çıkış kodu (exit code) bırakır. Kural basittir: 0 başarı, 0 dışındaki her şey hata. Gelenek olarak 1 genel hata, 2 hatalı kullanım (eksik/yanlış argüman) demektir. Zamanlayıcılar ve CI sistemleri ekrana değil bu sayıya bakar: kod 0 değilse alarm üretir.
Python'da sys.exit(kod) çağırınca SystemExit istisnası fırlatılır ve program o kodla biter. Hiç çağırmazsan ve hata da olmazsa kod 0'dır; yakalanmamış bir istisnada kod 1 olur.
import sys
def kontrol(satir_sayisi):
if satir_sayisi == 0:
print("Rapor boş, çıkılıyor", file=sys.stderr)
sys.exit(1)
print("Rapor tamam:", satir_sayisi, "satır")
for n in [120, 0]:
try:
kontrol(n)
except SystemExit as e:
print("Program şu kodla bitecekti:", e.code)
Standart girdi, çıktı ve hata
Her program üç "boru" ile doğar: standart girdi (stdin, klavyeden ya da başka programdan gelen veri), standart çıktı (stdout, asıl sonuç) ve standart hata (stderr, uyarı ve hata mesajları). print() varsayılan olarak stdout'a yazar; print(..., file=sys.stderr) ise stderr'e. İkisi ekranda aynı görünür ama ayrı borulardır ve ayrı yönlendirilebilir.
import sys, io
def ozetle(tutarlar, out=sys.stdout, err=sys.stderr):
gecerli = []
for t in tutarlar:
if t < 0:
print(f"UYARI: negatif tutar atlandı: {t}", file=err)
else:
gecerli.append(t)
print(f"toplam={sum(gecerli)}", file=out)
hata_kutusu = io.StringIO() # stderr'i bir metin kutusunda yakala
ozetle([250, -40, 1200], err=hata_kutusu)
print("Yakalanan uyarılar:", hata_kutusu.getvalue().strip())
Akışı parametre yapmak (out, err) fonksiyonu test edilebilir kılar: testte io.StringIO verirsin, gerçekte sys.stdout. Kullanıcıdan girdi almayı (input()) "Kullanıcı girdisi ve tür dönüşümü" dersinde derinlemesine göreceksin.
Yönlendirme ve boru hattı
python rapor.py İzmir > rapor.txt # stdout'u dosyaya yaz (üzerine) python rapor.py Bursa >> rapor.txt # dosyanın sonuna ekle python rapor.py İzmir 2> hatalar.log # yalnız stderr'i dosyaya python rapor.py İzmir > out.txt 2>&1 # ikisini aynı dosyaya cat stok.log | grep HATA | wc -l # boru: birinin çıktısı diğerinin girdisi type stok.log | findstr HATA # Windows cmd karşılığı python temizle.py < ham.csv > temiz.csv # stdin'i dosyadan oku
| (boru, pipe) Unix felsefesinin kalbidir: her araç tek işi iyi yapar, borularla birleşir. Senin betiğin de iyi bir boru vatandaşı olsun: veriyi stdout'a, mesajları stderr'e yaz. Böylece > sonuc.csv yapan kişi dosyasında uyarı satırı bulmaz.
Ortam değişkenleri
Ortam değişkenleri (environment variables), işletim sisteminin programa verdiği anahtar-değer ayarlarıdır: PATH, HOME, veritabanı adresi, ortam adı (geliştirme/canlı). Python'da os.environ bir sözlük gibi davranır. Değer her zaman metindir ve olmayabilir; bu yüzden .get(ad, varsayilan) kullan.
export ANADOLU_ORTAM=canli # macOS/Linux (bu oturum için) set ANADOLU_ORTAM=canli # Windows cmd $env:ANADOLU_ORTAM = "canli" # PowerShell
import os
ortam = os.environ.get("ANADOLU_ORTAM", "gelistirme")
zaman_asimi = int(os.environ.get("ZAMAN_ASIMI", "30"))
print("Ortam:", ortam, "| zaman aşımı:", zaman_asimi, "sn")
print("PATH tanımlı mı?", "PATH" in os.environ)
os.environ["ANADOLU_ORTAM"] = "test" # yalnız bu süreç ve alt süreçleri görür
print("Yeni değer:", os.environ["ANADOLU_ORTAM"])
os.environ["SIFRE"] değişken yoksa KeyError verir, .get() ise None ya da varsayılanı döndürür.python -m mutabakat 2026-10-27 --sube 0412 > mutabakat.csv 2> mutabakat.log. Zamanlayıcı her gece bu komutu koşturur; veritabanı adresi BANKA_DB_URL ortam değişkeninden gelir. Betik çıkış kodu 0 dönerse CSV muhasebeye gider; 1 dönerse nöbetçi geliştiriciye otomatik bildirim düşer ve log dosyası incelenir. Hiç kimse ekrana bakmaz; her şeyi çıkış kodu, stdout ve stderr anlatır.Bu bölüm, bir sunucuya bağlandığında ya da büyük bir log dosyasıyla karşılaştığında işini görecek Linux kabuk (shell) araçlarını tanıtıyor. Amaç her bayrağı ezberlemek değil; hangi aracın hangi işe yaradığını bilip gerektiğinde man grep ya da grep --help ile ayrıntıya bakabilmek. Her aracın yanında Python karşılığını da göreceksin: ikisini birlikte bilmek, ne zaman tek satırlık komutun ne zaman betiğin daha uygun olduğunu seçmeni sağlar. Windows'ta bu komutları WSL (Windows Subsystem for Linux) ya da Git Bash ile kullanabilirsin.
Metin araçları: grep, sed, awk, jq
| Komut | Ne yapar? | Python karşılığı |
|---|---|---|
grep -i "hata" app.log | Deseni içeren satırları yazar (-i büyük/küçük harf duyarsız, -v tersini, -c sayısını, -r klasörde özyinelemeli) | [s for s in satirlar if "hata" in s.lower()] |
sed 's/eski/yeni/g' dosya | Satır satır bul-değiştir (g olmadan satırdaki yalnız ilk eşleşme) | re.sub("eski", "yeni", satir) |
awk -F, '{t+=$10} END {print t}' | Satırları sütunlara böler ($1, $2... 1'den başlar), hesap yapar | sum(float(s.split(",")[9]) ...) |
jq '.items[].fiyat' veri.json | JSON içinden alan seçer, süzer | [x["fiyat"] for x in d["items"]] |
sort | uniq -c | sort -rn | Sırala, ardışık tekrarları say, sayıya göre azalan diz | Counter(...).most_common() |
cut -d, -f2, head, tail -f, wc -l | Sütun kes, ilk/son satırlar, canlı izle, satır say | split, dilim, len |
# En çok 404 alan 5 yol (erişim logunda 7. alan yol, 9. alan durum kodu)
awk '$9 == 404 {print $7}' access.log | sort | uniq -c | sort -rn | head -5
# Son 1 saatteki hataları canlı izle
tail -f app.log | grep --line-buffered ERROR
# Lezzet Kurye siparişlerinde duruma göre teslimat ücreti toplamı
awk -F, 'NR > 1 {t[$8] += $10} END {for (k in t) print k, t[k]}' lk_orders.csv
# API yanıtından yalnız adlar
curl -s https://api.example.com/urunler | jq -r '.[] | select(.stok == 0) | .ad'
Aynı awk satırının Python karşılığı aşağıda; gerçek lk_orders dosyasında çalışıyor. Kısa ve tek seferlik sorularda kabuk daha hızlıdır; mantık büyüyünce, test gerekince ya da Windows'ta da çalışması gerekince Python kazanır.
import veri
yol = veri.yol("lezzet_kurye", "lk_orders")
toplam = {}
with open(yol, encoding="utf-8") as f:
next(f) # NR > 1: başlığı atla
for satir in f:
alan = satir.rstrip("\n").split(",") # -F,
toplam[alan[7]] = toplam.get(alan[7], 0) + float(alan[9]) # $8, $10
for durum, tutar in sorted(toplam.items()):
print(durum, round(tutar, 2))
Dosya bulmak ve toplu işlem: find ve xargs
find . -name "*.py" -mtime -1 # son 1 günde değişen .py dosyaları
find /var/log -name "*.log" -size +100M # 100 MB'tan büyük loglar
find . -name "__pycache__" -type d -prune -exec rm -rf {} +
grep -rl "TODO" src/ | xargs wc -l # TODO içeren dosyaların satır sayısı
find . -name "*.csv" -print0 | xargs -0 -n 1 python temizle.py # boşluklu adlara dayanıklı
xargs, girdideki her satırı bir komutun argümanına çevirir. Dosya adlarında boşluk olabileceği için -print0 ve -0 çiftini alışkanlık edin. Python'da karşılığı Path(".").rglob("*.py") ve döngüdür.
İzinler ve chmod
ls -l çıktısındaki -rwxr-x--- üç gruba ayrılır: sahip (user), grup (group), diğerleri (other). Her grupta okuma (r=4), yazma (w=2), çalıştırma (x=1) vardır; toplamları sayısal izni verir: rwx=7, r-x=5, ---=0 → 750.
import stat
for izin in (0o755, 0o644, 0o600, 0o750):
print(oct(izin)[2:], stat.filemode(stat.S_IFREG | izin))
chmod 755 deploy.sh # sahibi her şey, diğerleri oku+çalıştır chmod u+x betik.py # sembolik: sahibine çalıştırma ekle chmod 600 ~/.ssh/id_ed25519 # özel anahtar yalnız sahibine açık olmalı chown app:app /srv/market -R
ls -l) ve hangi kullanıcıyla çalıştığını (whoami) kontrol et; gerekeni ver. SSH özel anahtarının izni geniş olursa ssh onu kullanmayı reddeder.Süreçler, uzak sunucu ve tmux
| Komut | İş |
|---|---|
ps aux | grep gunicorn, top / htop | Çalışan süreçleri, CPU ve bellek kullanımını gör |
kill 4321 / kill -9 4321 | SIGTERM: "düzgün kapan" ricası; SIGKILL: anında öldür (temizlik yapılamaz, son çare) |
ssh-keygen -t ed25519, ssh-copy-id kul@sunucu | Anahtar çifti üret, açık anahtarı sunucuya koy; parolasız ve güvenli giriş |
ssh kul@sunucu, scp rapor.csv kul@sunucu:/srv/veri/ | Uzak kabuk; dosya kopyala (büyük ve tekrarlı kopyada rsync -avz) |
tmux new -s etl, Ctrl+b d, tmux attach -t etl | Oturum aç, ayrıl (bağlantı kopsa da iş sürer), geri bağlan |
Uzun süren bir eğitim ya da ETL işini SSH üzerinden başlatıp bağlantın koparsa iş ölür; tmux (ya da screen) içinde başlatırsan oturum sunucuda yaşamaya devam eder. Python tarafında SIGTERM'i yakalayıp yarım işi kaydetmek docker-deploy dersindeki "düzgün kapanma" konusudur.
ssh ile bağlan, df -h ile disk, free -m ile bellek, ps/top ile süreçlere bak, tail -n 200 app.log | grep -i error ile son hataları oku. Bu akışı rahat yapabilmek mühendis, backend ve ML rollerinde mülakatlarda da sorulur.Test edilebilir komut satırı kalıbı
Profesyonel Python betiklerinin neredeyse hepsi aynı iskeleti kullanır: tüm iş main(argv) içinde yapılır, fonksiyon çıkış kodunu döndürür, sys.exit yalnızca en dışta bir kez çağrılır.
import sys
def main(argv, env=None, out=sys.stdout, err=sys.stderr):
env = env if env is not None else {}
if len(argv) != 2:
print("Kullanım: stok.py <depo>", file=err)
return 2
esik = int(env.get("STOK_ESIK", "10"))
print(f"{argv[1]} deposu, eşik {esik}", file=out)
return 0
kod = main(["stok.py", "Gebze"], env={"STOK_ESIK": "25"})
print("Çıkış kodu:", kod)
# Gerçek betikte en altta:
# if __name__ == "__main__":
# sys.exit(main(sys.argv, dict(os.environ)))
Bu kalıbın üç faydası var: testte argv, ortam ve akışları istediğin gibi verirsin; çıkış kodu tek yerden yönetilir; modül içe aktarıldığında (import) hiçbir şey kendiliğinden çalışmaz.
Tırnaklar ve boşluklu argümanlar
Terminal argümanları boşluktan böler: python rapor.py Kahraman Maraş iki argüman verir. Tek argüman için tırnak kullan: "Kahraman Maraş". Python'da bir komut satırını terminal gibi bölmek için shlex.split('rapor.py "Kahraman Maraş" 7') kullanılır. Alt süreç (subprocess) çalıştırırken komutu tek metin yerine liste olarak vermek de aynı nedenle güvenlidir.
Çıkış kodu sözleşmesi
Ekibinle bir tablo üzerinde anlaş: 0 başarı, 1 iş hatası (veri bozuk), 2 kullanım hatası, 3 dış sistem erişilemiyor gibi. Zamanlayıcı kuralları ("kod 3 ise 10 dakika sonra yeniden dene") bu sözleşmeye dayanır. Unix'te 126-255 arası kodlar kabuğa ayrılmıştır; kendi kodlarını küçük tut.