Mert Özen Her Satırda Daha İleri
Geliştirici Araçları Terminal ve Command Line — #01

Terminal, Shell, Console ve Prompt: Kavramları Ayırmak ve İşletim Sistemine Göre Doğru Kurulum

Mert Özen 14 Kas 2026 13 dk 1 görüntülenme
Terminal, Shell, Console ve Prompt: Kavramları Ayırmak ve İşletim Sistemine Göre Doğru Kurulum

Terminal emulator pencereyi çizer, shell yazdığın satırı yorumlar, aradaki PTY tuşları ve sinyalleri taşır; ikisi ayrı program olduğu için ayrı ayrı kurulur ve güncellenir. bash, zsh ve PowerShell'in hangi ayar dosyasını ne zaman okuduğunu gerçek bir denemeyle gösteriyor, Windows'ta PowerShell 7.6 + Windows Terminal + WSL 2, macOS'ta zsh için karar tablosu ve repo'nun nerede durması gerektiğini anlatıyoruz.

Sürüm notu: Bu yazıdaki komutlar Ubuntu 24.04 üzerinde bash 5.2, zsh 5.9 ve PowerShell 7.6.10 ile, .NET 10 SDK ve Node.js 24 kurulu temiz bir ortamda denenmiştir. Windows Terminal, WSL (Windows Subsystem for Linux; Windows içinde gerçek bir Linux çekirdeği çalıştıran katman) ve macOS'a özgü davranışlar Microsoft ve Apple'ın resmi dokümanlarından aktarıldı; bu kısımlar Windows ve macOS üzerinde çalıştırılmadı.

Önce Tahmin Et

Boş bir home klasöründe yedi dosya var: .bash_profile, .bashrc, .profile, .zshenv, .zprofile, .zshrc, .zlogin. Her biri yalnızca kendi adını ekrana yazıyor. PowerShell için de Microsoft.PowerShell_profile.ps1 aynı şeyi yapıyor. Şimdi şu komutları sırayla çalıştırıyorsun:

$ bash -c true
$ bash -i -c true
$ bash -l -c true
$ zsh -i -c true
$ zsh -l -c true
$ pwsh -c 0

Her satır hangi dosyaları okur? Özellikle iki soruya cevap ver: bash -l hem .bash_profile'ı hem .profile'ı okur mu, ve pwsh -c gibi etkileşimsiz bir komut profile dosyasını yükler mi? Cevaplar yazının ortasındaki tabloda. Bu tablo, "PATH'i (shell'in komutları aradığı klasör listesi) .zshrc'ye yazdım ama VS Code'un task'ı görmüyor" ya da "CI'da alias çalışmıyor" gibi şikâyetlerin çoğunu açıklıyor.

Dört Kavram: Terminal, Shell, Console ve TTY

Günlük konuşmada "terminali aç", "shell'e yaz", "console'da çalıştır" aynı anlama geliyor gibi kullanılıyor. Kurulum ve sorun çözme sırasında ise bu ayrım önemli, çünkü her biri ayrı bir program ve ayrı bir ayar dosyası demek.

01-terminal-shell-pty-tr.png

Terminal emulator ekrandaki penceredir: Windows Terminal, macOS'taki Terminal.app, iTerm2, Ghostty ya da VS Code'un integrated terminal'i. Yaptığı iş karakterleri çizmek, fontu ve renkleri yönetmek, sekme ve pane açmak, kopyala-yapıştırı ele almaktır. Komut çalıştırmaz, history tutmaz, cd'nin ne olduğunu bilmez.

Shell, terminalin içinde çalışan ve senin yazdığın satırı yorumlayan programdır: bash, zsh, fish, PowerShell (pwsh) ya da Windows'un eski cmd.exe'si. Prompt'u basar, satırı düzenlemeni sağlar, history ve completion'ı yönetir, sonra komutu çalıştırmak için yeni process'ler başlatır.

İkisinin arasında işletim sisteminin sağladığı bir katman var. Unix tarafında buna PTY (pseudo-terminal; yazılımla taklit edilen sanal terminal cihazı) denir. Adı eski TTY'den (teletypewriter; bilgisayara bağlı fiziksel yazıcılı terminal) gelir. Ctrl-C'ye basınca çalışan process'e SIGINT (process'e "dur" diyen kesme sinyali) gönderen, pencere boyutunu programlara bildiren bu katmandır. Windows'ta aynı görevi Windows 10'dan beri ConPTY (Windows'un pseudo-console API'si) yapar.

Console kelimesi bağlama göre değişir. Windows'ta eskiden siyah pencereyi çizen conhost.exe'ye "console host" deniyordu; .NET'te Console.WriteLine standart çıktıya yazmak demek. Bu seride console'u "bir programın standart girdisi ve çıktısı" anlamında kullanacağız.

Teoride kalmasın diye bir terminal açıp iç içe üç shell başlattım ve aynı PTY'ye bağlı process'lere baktım:

$ tty
/dev/pts/0
$ zsh -f
dev% pwsh -NoProfile -NoLogo
PS /home/ada/bookstore> ps -o pid,ppid,tty,comm --forest -t (tty)
    PID    PPID TT       COMMAND
     22      21 pts/0    bash
     44      22 pts/0     \_ zsh
     51      44 pts/0         \_ pwsh
     78      51 pts/0             \_ ps

Dört process aynı /dev/pts/0'a bağlı ve her biri bir öncekinin child'ı. Terminal emulator bu listede yok, çünkü PTY'nin öbür ucunda duruyor. Windows Terminal'de WSL sekmesi ya da macOS Terminal'de aynı denemeyi yaparsan sonuç aynı olur. Buradan çıkan pratik ders şu: Windows Terminal'i güncellemek PowerShell'i güncellemez, iTerm2'ye geçmek zsh ayarlarını değiştirmez. İkisi ayrı program, ayrı sürüm, ayrı ayar dosyası.

Bir de şu yaygın yanılgı var: $SHELL değişkeni şu an içinde olduğun shell'i değil, kullanıcının varsayılan login shell'ini gösterir. Yukarıdaki zsh'nin içinde bakınca:

dev% echo $SHELL; echo $0; ps -p $$ -o comm=
/bin/bash
zsh
zsh

Hangi shell'de olduğunu öğrenmek için echo $0 ya da ps -p $$ kullan; PowerShell'de $PSVersionTable.PSEdition ve $PSVersionTable.PSVersion sana hem sürümü hem de Windows PowerShell mi PowerShell 7 mi olduğunu söyler.

Prompt: Shell'in Sana Sorduğu Soru

Prompt, shell'in "senden komut bekliyorum" demek için bastığı metindir. Terminal değil shell üretir, o yüzden her shell'in kendi tanımı var. Üç shell'in hiçbir ayar yapılmamış hâlleri:

$ bash -ic 'echo "$PS1"'
${debian_chroot:+($debian_chroot)}\u@\h:\w\$
$ zsh -f -ic 'print -r -- $PROMPT'
%m%#
$ pwsh -NoProfile -c '(Get-Command prompt).Definition.Trim().Split([char]10)[0]'
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";

bash'te prompt PS1 değişkenidir; \u kullanıcı, \h host adı, \w çalışma klasörü. zsh'de PROMPT (ya da PS1) değişkenidir ve %m host adı, %# normal kullanıcıda %, root'ta # basar. macOS'ta yeni bir terminal açınca gördüğün % işareti buradan gelir. PowerShell'de ise prompt bir değişken değil, prompt adında bir fonksiyondur; ne döndürürse ekrana o basılır. Prompt'u git branch'ini ve .NET sürümünü gösterecek şekilde zenginleştirmek bu serinin zsh ve PowerShell yazılarının konusu.

Bu yazıda bash/zsh komutlarını $, PowerShell komutlarını PS> ile başlatacağım; çıktıda kendi makinemin kullanıcı adı ya da yolları yerine temiz bir ortamın değerleri (ada@dev, /home/ada) görünecek.

Login ve Interactive Shell: Hangi Dosya Ne Zaman Okunur

Shell başlarken hangi ayar dosyasını okuyacağına iki soruya bakarak karar verir. Interactive mi, yani bir insan prompt'a mı yazıyor? Login mi, yani bu oturumun ilk shell'i mi? -i interactive, -l login anlamına gelir. Baştaki deneyin gerçek sonucu:

$ bash -c true
$ bash -i -c true
  -> .bashrc
$ bash -l -c true
  -> .bash_profile
$ zsh -c true
  -> .zshenv
$ zsh -i -c true
  -> .zshenv
  -> .zshrc
$ zsh -l -c true
  -> .zshenv
  -> .zprofile
  -> .zlogin
$ zsh -il -c true
  -> .zshenv
  -> .zprofile
  -> .zshrc
  -> .zlogin
$ pwsh -c 0
  -> Microsoft.PowerShell_profile.ps1
0
$ pwsh -NoProfile -c 0
0

Sonuçları tabloya dökünce kural netleşiyor:

BaşlatmabashzshPowerShell 7
Script, etkileşimsizhiçbiri.zshenv$PROFILE (-NoProfile yoksa)
Interactive.bashrc.zshenv, .zshrc$PROFILE
Loginilk bulunan: .bash_profile, .bash_login, .profile.zshenv, .zprofile, .zloginlogin kavramı yok
Login + interactiveyine yalnız login dosyasıdördü de$PROFILE

Tahminlerin cevabı: bash login olarak açıldığında .bash_profile, .bash_login ve .profile arasından yalnızca bulduğu ilkini okur. Ubuntu'nun hazır .profile dosyası içinden .bashrc'yi çağırır; sen bir gün .bash_profile oluşturursan .profile artık hiç okunmaz ve alias'ların bir anda kaybolur. PowerShell ise login/interactive ayrımı yapmaz: -NoProfile vermezsen pwsh -c ile çalışan tek satırlık komut bile profile'ını yükler. GitHub Actions'ta shell: pwsh adımları pwsh -command ". '{0}'" şeklinde çalıştığı için, runner'da bir profile varsa o da yüklenir.

Pratikteki karşılığı şöyle: Linux masaüstündeki terminaller çoğunlukla login olmayan interactive shell açar, SSH (Secure Shell; uzak makineye şifreli bağlantı) oturumları ise login shell'dir. Ubuntu'nun hazır .profile'ı .bashrc'yi çağırdığı için iki durumda da bash ayarlarının yeri .bashrc'dir. macOS'ta Terminal.app ve iTerm2 her pencerede varsayılan olarak login shell açar; zsh login + interactive olduğu için dört dosyayı da okur. Bu yüzden zsh'de kural basit: PATH ve ortam değişkenleri .zprofile'a, alias, fonksiyon, prompt ve completion .zshrc'ye. .zshenv her zsh çağrısında, script'lerde bile okunduğu için oraya yavaş ya da ekrana yazan bir şey koyma.

macOS: zsh Varsayılan, bash 3.2 Eski

Apple'ın destek sayfasına göre macOS Catalina'dan (10.15) beri yeni kullanıcı hesaplarında varsayılan login ve interactive shell zsh. Sistemle gelen bash hâlâ duruyor ama 3.2 sürümünde kaldı; bash 4 ve sonrası GPLv3 (GNU General Public License sürüm 3) lisanslı olduğu için Apple yeni sürümleri sisteme koymuyor. Güncel bash 5.3'tür (Temmuz 2025). macOS'ta bash'i açınca varsayılan shell'in artık zsh olduğunu söyleyen bir uyarı görürsün; Apple bunu BASH_SILENCE_DEPRECATION_WARNING=1 ile kapatmayı anlatıyor.

Benim önerim, macOS'ta zsh'de kalmak ve bash'e yalnızca script çalıştırmak için ihtiyaç duymak. Script'lerin bash 4+ özelliklerine (associative array, mapfile) ihtiyaç duyuyorsa güncel bash'i Homebrew ile kur:

$ brew install bash zsh git
$ /opt/homebrew/bin/bash --version | head -1

Homebrew Apple Silicon Mac'lerde /opt/homebrew, Intel Mac'lerde /usr/local altına kurar ve sistem dosyalarına dokunmaz. Varsayılan shell'i değiştirmek için chsh -s kullanılır; Apple'a göre verdiğin yol /etc/shells dosyasında listelenmiş olmalı. Script'lerin başına #!/bin/bash yerine #!/usr/bin/env bash yazarsan PATH'teki güncel bash kullanılır. Script yazmanın ayrıntıları Bash Scripting serisinin konusu.

Windows: Windows PowerShell 5.1, PowerShell 7.6 ve Windows Terminal

Windows'ta iki ayrı PowerShell var ve en çok karışıklık buradan çıkıyor. Windows PowerShell 5.1 (powershell.exe) Windows'un bir parçası, .NET Framework üzerinde çalışır ve artık yeni özellik almaz; desteği Windows'un yaşam döngüsüne bağlı. PowerShell 7 (pwsh.exe) ayrı kurulan, açık kaynak ve platformlar arası sürüm. Microsoft'un destek sayfasına göre güncel LTS (Long Term Support; uzun süre güvenlik güncellemesi alan sürüm) PowerShell 7.6: 18 Mart 2026'da çıktı, .NET 10 üzerinde çalışır ve desteği 14 Kasım 2028'e kadar sürer. Önceki LTS olan 7.4 ile ara sürüm 7.5'in desteği 10 Kasım 2026'da bitti. Kurulum winget ile tek satır:

PS> winget install --id Microsoft.PowerShell --source winget
PS> winget install --id Microsoft.WindowsTerminal --source winget

İki PowerShell yan yana durur, birini kurmak diğerini silmez. Profile dosyaları da ayrıdır: 5.1 Documents\WindowsPowerShell, 7 Documents\PowerShell klasörünü kullanır. Hangisinde olduğunu kontrol etmek için:

PS> $PSVersionTable.PSVersion.ToString(); $PSVersionTable.PSEdition
7.6.10
Core

5.1'de PSEdition değeri Desktop olur. Windows 11'de 22H2 güncellemesinden beri varsayılan terminal uygulaması Windows Terminal. Windows Terminal kurulu shell'leri bulup her biri için otomatik bir profile oluşturur: Windows PowerShell, Command Prompt, PowerShell 7 ve kurulu her WSL dağıtımı. Yapman gereken iki şey var: Settings'te Startup bölümünden varsayılan profile'ı PowerShell 7 yapmak ve her profile'ın başlangıç klasörünü ayarlamak. Aynı ayarlar settings.json'da şöyle görünür (Windows Terminal dokümanındaki alan adlarıyla, guid'ler kısaltıldı):

{
  "defaultProfile": "{574e775e-...}",
  "profiles": {
    "list": [
      {
        "guid": "{574e775e-...}",
        "name": "PowerShell 7",
        "commandline": "pwsh.exe -NoLogo",
        "startingDirectory": "%USERPROFILE%\\source\\bookstore"
      },
      {
        "name": "Ubuntu",
        "commandline": "wsl.exe -d Ubuntu"
      }
    ]
  }
}

VS Code'un integrated terminal'i de ayrı bir terminal emulator'dür ve kendi varsayılan profile ayarına sahiptir (terminal.integrated.defaultProfile.windows). Windows Terminal'de PowerShell 7'yi seçip VS Code'da 5.1 ile kalmak sık görülen bir durum; ikisini de ayrı ayrı kontrol et.

WSL 2: Linux Toolchain'i ve Repo'yu Nerede Tutmalı

WSL 2, .NET + Angular geliştiricisine Windows'u bırakmadan Linux ortamı verir. Docker Desktop, Linux container'ları ve bash script'leri burada doğal çalışır. Kurulum yönetici olarak açılmış bir PowerShell'de tek komut; dokümana göre varsayılan dağıtım Ubuntu:

PS> wsl --install
PS> wsl --list --verbose

Kurulumdan sonraki en önemli karar, kodun hangi dosya sisteminde duracağı. Microsoft'un WSL dokümanı bunu açıkça söylüyor: Linux komut satırında çalışacaksan dosyaları WSL dosya sisteminde (/home/ada/bookstore), Windows araçlarıyla çalışacaksan Windows dosya sisteminde (C:\Users\...) tut; işletim sistemleri arasında çapraz çalışmaktan kaçın. WSL içinden /mnt/c altındaki bir klasör Windows diskine bir köprü üzerinden erişir ve binlerce küçük dosyaya dokunan işler (npm install, dotnet restore, git status) belirgin şekilde yavaşlar.

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için pratik kural şöyle:

  • Visual Studio 2026 ve Windows'taki dotnet/ng ile çalışıyorsan repo C:\Users\...\source\bookstore'da, shell PowerShell 7.
  • WSL'deki bash/zsh ile, Linux'taki dotnet ve node ile çalışıyorsan repo /home/ada/bookstore'da; VS Code'u WSL içinden code . ile aç.
  • Windows Explorer'dan WSL dosyalarına \\wsl.localhost\Ubuntu\home\ada yolundan ya da WSL içinde explorer.exe . yazarak ulaşırsın.

Aynı repo'yu iki tarafta aynı anda kullanmaya çalışma: node_modules ve bin/obj içindeki native ve platforma özgü dosyalar karışır, Windows'ta kurulan paketler Linux'ta çalışmaz.

Karar Tablosu: Hangi İşletim Sisteminde Hangi Shell

.NET + Angular geliştiricisi için benim kullandığım ve ekiplere önerdiğim tablo:

MakineTerminalVarsayılan shellNe zaman ikinci shell
Windows 11, Visual Studio ağırlıklıWindows TerminalPowerShell 7.6Docker/Linux script'leri için WSL 2 Ubuntu (bash)
Windows 11, VS Code + container ağırlıklıWindows TerminalWSL 2 Ubuntu içinde bash ya da zshWindows'a özgü işler (winget, IIS (Windows'un web sunucusu), registry) için PowerShell 7.6
macOSTerminal.app, iTerm2 ya da GhosttyzshWindows'lu ekip arkadaşıyla ortak script için PowerShell 7.6 (brew install powershell)
Linux masaüstüDağıtımın terminali ya da Ghosttybash (ya da zsh)Ekip PowerShell script'leri kullanıyorsa pwsh

Tablodaki asıl fikir şu: varsayılan shell'i kodun durduğu dosya sistemi ve kullandığın araçlar belirler, kişisel zevk değil. cmd.exe'yi geliştirme için varsayılan yapmanın bugün bir gerekçesi yok; eski .bat dosyalarını çalıştırmak için var olmaya devam ediyor. Ghostty macOS ve Linux'ta çalışır, iTerm2 yalnızca macOS'ta; seçimi terminalin özellikleri değil, shell kararı belirlemeli.

Üretimde Dikkat

Bu seri geliştirici makinesini anlatıyor, ama ayar dosyalarının nerede okunduğu bilgisi CI ve container'da da karşına çıkar. GitHub Actions'ta shell belirtmediğin bir run adımı Linux'ta bash -e {0}, shell: bash dediğinde bash --noprofile --norc -eo pipefail {0} ile çalışır. İkisi de interactive değildir, yani .bashrc'deki alias'lar, fonksiyonlar ve PATH eklemeleri orada yoktur. Dockerfile'daki RUN satırları da /bin/sh -c ile çalışır. Kural basit: bir komut CI'da ya da container'da da çalışacaksa alias'a ya da .bashrc'ye güvenme, tam komutu yaz.

PowerShell tarafında durum tersi: -NoProfile yoksa profile her zaman yüklenir. Profile'ında yavaş bir modül yükleme ya da ekrana yazı basan bir satır varsa, onu çağıran her script ve her build adımı bundan etkilenir. Script'leri ve build adımlarını pwsh -NoProfile -File build.ps1 şeklinde çağırmayı alışkanlık hâline getir.

Ekipte "bende çalışıyor" tartışmalarının bir kısmı sürüm farkından gelir. Yeni katılan biri için ilk kontrol listesi kısa: $PSVersionTable.PSVersion 7.6 mı, dotnet --list-sdks repo'daki global.json ile uyuşuyor mu, node -v Angular CLI'nin istediği aralıkta mı.

Sık Düşülen Tuzaklar

Birincisi WSL'de /mnt/c üzerinde çalışmak. Repo'yu Windows tarafında klonlayıp WSL'den cd /mnt/c/Users/.../bookstore ile girip npm install çalıştırmak çalışır ama yavaştır; dosya izleme (ng serve, dotnet watch) de Windows diskindeki değişiklikleri her zaman yakalamaz. Çözüm repo'yu /home altına klonlamak.

İkincisi PATH'te aynı aracın iki kopyası. WSL'de Windows'un PATH'i varsayılan olarak Linux PATH'ine eklenir; Windows'taki node.exe ya da dotnet.exe Linux'takinin önüne geçebilir. Linux'ta da sık olur: Node'u resmi paketten kurup bir de apt install nodejs yapınca iki Node oluşur. Temiz ortamda bunu bilerek ürettim:

$ type -a node
node is /usr/local/bin/node
node is /usr/bin/node
node is /bin/node
$ node -v; /usr/bin/node -v
v24.22.3
v18.19.1
$ PATH=/usr/bin:$PATH; hash -r; ng version | head -2
Node.js version v18.19.1 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.

PATH'in sırası hangi kopyanın çalışacağını belirler; hash -r bash'in komut yolu cache'ini temizler. PowerShell'de aynı kontrol Get-Command node -All, Windows'un klasik aracıyla where.exe node. .NET için dotnet --list-sdks ve dotnet --info içindeki "Base Path" satırı hangi kurulumun kullanıldığını söyler.

Üçüncüsü .bash_profile oluşturup .bashrc'nin okunmamasına şaşırmak; yukarıdaki tablo bunu anlatıyor. Dördüncüsü Windows Terminal'de varsayılan profile'ın hâlâ Windows PowerShell 5.1 olması: PowerShell 7'yi kurup her şeyi doğru yaptığını düşünürken modüllerin, profile'ın ve davranışın 5.1'den geldiğini fark etmemek.

Küçük Bir Deneme

★☆☆ Kendi makinende echo $0 (PowerShell'de $PSVersionTable.PSEdition), echo $SHELL ve tty komutlarını çalıştır. Terminal emulator'ünün, varsayılan shell'inin ve şu an içinde olduğun shell'in adını ayrı ayrı yaz.

★★☆ Boş bir test klasörünü HOME yapıp (HOME=/tmp/h1 bash -l -c true gibi) yazıdaki dosya okuma deneyini kendi bash ya da zsh'inde tekrarla. Sonra .bash_profile'ı silip yalnız .profile bırak ve sonucun nasıl değiştiğine bak. PowerShell'de $PROFILE | Select-Object * ile dört profile yolunu listele.

★★★ Bookstore repo'sunu WSL kullanıyorsan önce /mnt/c altına, sonra /home altına klonla ve iki yerde time npm ci ile time dotnet restore çalıştır. Farkı not et ve hangi tarafta çalışacağına buna göre karar ver. type -a dotnet node git ile PATH'te aynı aracın birden fazla kopyası olup olmadığını da kontrol et.

Artık hangi programın ne iş yaptığını ve ayarların nereden okunduğunu biliyorsun. Bir sonraki yazıda bu shell'lerin içinde hızlanmaya başlıyoruz: satır düzenleme kısayolları, history arama, secret'ları history'ye düşürmemek ve dotnet, ng, git için tab completion.