Komut ikamesi, bir komutun standart çıktısını yakalayıp komutun yerine koymaktır: degisken=$(komut) yazdığınızda Bash komutu çalıştırır, çıktısının sonundaki yeni satırları siler ve kalan metni değişkene atar. Eski yazım ters tırnaktır (`komut`); kılavuz bu biçimi eskimiş (deprecated) olarak işaretler. Komut bir alt kabukta çalıştığı için içeride yapılan değişken değişiklikleri ve cd dışarıya yansımaz. Sonucu her zaman çift tırnak içinde kullanın: "$(komut)".
Bu yazıda iki yazım arasındaki farkları, sondaki yeni satırların silinmesini ve bunun nasıl korunacağını, tırnak ve sözcük bölme etkisini, iç içe kullanımı, $(< dosya) kısayolunu, çıkış durumunun nereye gittiğini, set -e ile ilişkisini ve kılavuzun 5.3 sürümünde anlattığı ${ komut; } biçimini ele alıyoruz. Serinin tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz. Örnekler GNU Bash 5.2.37 ile çalıştırıldı.
Temel kullanım ve sondaki yeni satırlar

Kılavuza göre Bash komut ikamesini, komutu bir alt kabuk ortamında çalıştırıp ikamenin yerine komutun standart çıktısını koyarak yapar ve bunu yaparken sondaki bütün yeni satırları siler. Metnin içindeki yeni satırlar silinmez, ama tırnaksız kullanımda sözcük bölme sırasında kaybolabilir:
selam=$(echo "Merhaba")
echo "$selam dünya" # çıktı: Merhaba dünya
icerik=$(printf 'satır 1\nsatır 2\n\n\n')
echo "[$icerik]"
# çıktı:
# [satır 1
# satır 2]printf sonda üç yeni satır üretti ama değişkene yalnızca iki satırlık metin girdi; kapanış köşeli parantezi ikinci satırın hemen arkasında görünüyor. Çoğu durumda bu tam istediğiniz şeydir: echo gibi komutların sonuna eklediği yeni satır değişkene karışmaz. Ancak bir dosyanın içeriğini birebir korumanız gerekiyorsa, örneğin sonunda boş satırlar olan bir şablonu, bu silme işlemi veriyi değiştirir.
Sondaki yeni satırları korumak için yaygın bir hile vardır: komutun arkasından tek bir karakter yazdırırsınız, böylece silinecek yeni satır kalmaz; sonra o karakteri parametre genişletmeyle atarsınız:
deger=$(printf 'son\n\n'; echo x)
deger=${deger%x}
printf '%q\n' "$deger" # çıktı: $'son\n\n' printf '%q' değeri kaçış dizileriyle gösterdiği için iki yeni satırın da korunduğunu görebiliyoruz. ${deger%x} sondaki x karakterini siliyor; bu operatörü Bash string işlemleri yazısında anlatıyoruz.
Tırnak, sözcük bölme ve dosya adı genişletme
Kılavuz, ikame çift tırnak içinde değilse sonucun sözcük bölmeye ve dosya adı genişletmeye uğradığını, çift tırnak içindeyse bu iki adımın uygulanmadığını söyler. Bu kural pratikte iki ayrı soruna yol açar. Birincisi, satırlar ve boşluklar ayırıcı olarak kullanılır ve metnin yapısı bozulur. İkincisi, çıktıda * gibi bir joker karakter varsa Bash onu dizindeki dosya adlarıyla değiştirir:
printf 'a b\nc\n' > liste.txt
for k in $(< liste.txt); do echo "<$k>"; done
# çıktı:
# <a>
# <b>
# <c>
echo $(< liste.txt) # çıktı: a b c
echo "$(< liste.txt)"
# çıktı:
# a b
# c
touch x.txt y.txt
echo $(echo '*.txt') # çıktı: liste.txt x.txt y.txt
echo "$(echo '*.txt')" # çıktı: *.txtİlk döngü dosyayı satır satır değil, sözcük sözcük dolaşıyor; a b satırı ikiye bölündü. Tırnaksız echo satırları tek satırda birleştirdi, tırnaklı kullanım ise orijinal yapıyı korudu. Son iki satır daha tehlikeli bir durumu gösteriyor: komutun çıktısı olan *.txt metni tırnaksız kullanılınca dizindeki üç dosyanın adına dönüştü. Dosyayı satır satır işlemek için doğru araç while IFS= read -r satir döngüsü ya da mapfile komutudur; bkz. Bash while döngüsü ve mapfile ve readarray.
Bu örnekte $(cat liste.txt) yerine $(< liste.txt) yazdık. Kılavuz, $(cat dosya) ikamesinin eşdeğer ve daha hızlı bir biçimi olarak $(< dosya) yazımını verir; Bash dosyayı harici bir program çalıştırmadan okur. Atamanın sağ tarafında ise tırnak gerekmez, çünkü kılavuzun parametreler bölümüne göre atamalarda sözcük bölme ve dosya adı genişletme yapılmaz; x=$(komut) güvenlidir. Yine de alışkanlık olarak her yerde tırnak kullanmak, bir argüman ya da [ ] içine taşıdığınızda hata yapmanızı önler. Sözcük bölme kurallarının kendisini tek tırnak ve çift tırnak farkı yazısında anlatıyoruz.
İç içe kullanım ve ters tırnağın sorunları
$( ) biçiminde iç içe kullanım doğaldır: içteki ikame önce çalışır, sonucu dıştakine argüman olur. Her seviyede kendi çift tırnaklarınızı yazabilirsiniz ve bunlar birbirine karışmaz. Ters tırnakla iç içe kullanmak için kılavuza göre içteki ters tırnakları ters eğik çizgiyle kaçırmanız gerekir:
ust=$(basename "$(dirname "/srv/app/config/ayar.ini")")
echo "$ust" # çıktı: config
eski=`echo \`echo iç içe\``
echo "$eski" # çıktı: iç içe
echo "$(echo 'a\\b')" # çıktı: a\\b
echo "`echo 'a\\b'`" # çıktı: a\bSon iki satır ince bir farkı gösteriyor. Kılavuza göre ters tırnak içinde ters eğik çizgi, arkasından $, ters tırnak ya da başka bir ters eğik çizgi geldiğinde özel anlam taşır; $( ) biçiminde ise parantezler arasındaki hiçbir karakter özel işlenmez. Bu yüzden aynı komut iki yazımda farklı sonuç verdi: ters tırnak, \\ çiftini tek bir ters eğik çizgiye indirdi. Bu tür sürprizler ve okunabilirlik, yeni kodda yalnızca $( ) kullanmak için yeterli nedendir.
Alt kabuk: değişken değişiklikleri kaybolur

Komut ikamesi alt kabukta çalışır ve kılavuzun çalışma ortamı bölümüne göre alt kabuktaki değişiklikler ana kabuğu etkilemez. İçeride yapılan atamalar, cd ile dizin değişikliği, tanımlanan fonksiyonlar ve açılan seçenekler ikame bitince kaybolur:
sayac=0
x=$(sayac=5; echo "içeride $sayac")
echo "$x, dışarıda $sayac" # çıktı: içeride 5, dışarıda 0Bu yüzden hem bir değer döndürüp hem de bir değişkeni güncelleyen bir fonksiyonu $( ) ile çağırmak işe yaramaz. Değişkeni güncellemek gerekiyorsa fonksiyonu doğrudan çağırıp sonucu bir isim referansıyla yazdırmak daha uygundur; bu yöntem Bash local değişken ve kapsam yazısında. Alt kabuğun neleri kopyalayıp neleri kopyalamadığı Bash subshell ve çalışma ortamı yazısında ayrıntılı anlatılıyor.
Çıkış durumu nereye gider?
Kılavuzun basit komut genişletmesi bölümüne göre, komut adı kalmayan bir satırda (yani yalnızca atamadan oluşan bir satırda) çıkış durumu, yapılan son komut ikamesinin durumudur. Bu sayede x=$(komut) satırından sonra $? kontrol edilebilir ve if x=$(komut); then yapısı çalışır. Ancak atama bir komutun argümanıysa durum o komuta aittir:
cikti=$(false); echo "atama: $?" # çıktı: atama: 1
cikti=$(echo veri; exit 3); echo "atama: $? [$cikti]" # çıktı: atama: 3 [veri]
export X=$(false); echo "export: $?" # çıktı: export: 0
metin="armut elma kiraz"
if satir=$(printf '%s\n' $metin | grep elma); then
echo "bulundu: $satir" # çıktı: bulundu: elma
fiexport X=$(false) satırında $? 0 çıktı, çünkü son çalışan komut export ve o başarılı oldu. local, declare ve readonly için de aynı durum geçerlidir. Çözüm, tanımı ve atamayı ayrı satırlara bölmektir. Son örnekteki pipe'ın durumu, son komut olan grep komutunun durumudur; pipe içindeki önceki komutların durumunu görmek için PIPESTATUS ve pipefail yazısına bakın.
set -e ve inherit_errexit
set -e açık bir betikte, komut ikamesinin içindeki bir hatanın betiği durduracağını düşünebilirsiniz. Kılavuz, POSIX modu dışında Bash'in komut ikamesi için açılan alt kabuklarda -e seçeneğini temizlediğini söyler. Yani ikamenin içindeki komutlar hata verse de alt kabuk çalışmaya devam eder; yalnızca ikamenin son komutunun durumu dışarıya yansır. inherit_errexit seçeneği açıldığında alt kabuk seçeneği devralır:
set -e
sonuc=$(false; echo "alt kabuk devam etti")
echo "$sonuc" # çıktı: alt kabuk devam etti
shopt -s inherit_errexit
sonuc=$(false; echo "bu basılmaz")
echo "buraya gelinmez"İlk ikamede false başarısız olduğu halde alt kabuk echo satırına geçti ve son komut başarılı olduğu için atama da başarılı sayıldı. inherit_errexit açıldıktan sonra alt kabuk false komutunda durdu, atama başarısız oldu ve set -e ana betiği de sonlandırdı; son satır hiç basılmadı ve betik 1 durumuyla bitti. Kılavuza göre bu seçenek POSIX modunda otomatik olarak açılır. set -e davranışının diğer istisnalarını set -euo pipefail ile güvenli betik yazısında anlatıyoruz.
Bash 5.3 kılavuzundaki ${ komut; } biçimi
Kılavuzun 5.3 sürümü, komut ikamesinin alternatif bir biçimini anlatıyor: ${ komut; }. Süslü parantezden sonra bir boşluk, sekme, yeni satır ya da | gelmesi ve kapanış parantezinin bir ayrılmış sözcüğün gelebileceği yerde, örneğin noktalı virgülden sonra olması gerekir. Kılavuza göre bu biçim komutu alt kabuk açmadan, mevcut çalışma ortamında çalıştırır ve çıktısını yine sondaki yeni satırları silerek yakalar. Yan etkiler kalıcıdır: içeride yapılan bir atama dışarıda da geçerlidir, exit kabuğun kendisinden çıkar.
Kılavuz bu biçimi isimsiz bir fonksiyon çalıştırmaya benzetir: içeride local ile yerel değişken tanımlanabilir ve return komutu ikameyi bitirir, ama konumsal parametreler dahil ortamın geri kalanı çağıranla paylaşılır. ${| komut; } varyantı ise komutun çıktısını değil, komut bittikten sonraki REPLY değişkeninin değerini verir; bu durumda sondaki yeni satırlar silinmez ve komutun standart çıktısı ekrana gitmeye devam eder. Kılavuzdaki örneklere göre aşağıdaki iki ifade de 12345 değerine genişler:
# Bash 5.3 kılavuzundan uyarlandı; GNU Bash 5.2.37 ile ÇALIŞMAZ
x=${ local X=12345; echo $X; }
y=${| REPLY=12345; }Kılavuz (5.3) bu biçimleri anlatıyor; 5.2.37 ile denediğimizde çalışmadı. İki satırı bir betikte çalıştırdığımızda Bash her biri için bad substitution hatası verdi ve değişkenler boş kaldı. Bu yüzden yukarıdaki kod için bir çıktı göstermiyoruz. Betiğinizi farklı sürümlerde çalıştıracaksanız bu biçimden kaçının ya da sürümü önceden kontrol edin; yöntemler Bash sürümünü öğrenme yazısında.
Uygulamalı örnek: günlük dosyasından kısa rapor
Komut ikamesini en sık bir komutun ürettiği sayıyı veya metni başka bir komutta kullanmak için yazarsınız. Aşağıdaki örnek küçük bir günlük dosyası oluşturuyor, hata satırlarını ve toplam satır sayısını iki ayrı ikameyle alıyor, sonra sayıyı sıfırla doldurarak bir dosya adı üretiyor:
mkdir -p gunluk
printf 'hata: disk\nbilgi: başladı\nhata: ağ\n' > gunluk/uygulama.log
hata_sayisi=$(grep -c '^hata' gunluk/uygulama.log)
toplam=$(wc -l < gunluk/uygulama.log)
echo "$toplam satırın $hata_sayisi tanesi hata" # çıktı: 3 satırın 2 tanesi hata
rapor="rapor_$(printf '%03d' "$hata_sayisi").txt"
echo "$rapor" # çıktı: rapor_002.txtgrep -c eşleşen satır sayısını, wc -l < dosya toplam satır sayısını yazar; dosyayı yönlendirmeyle verdiğimiz için wc çıktısına dosya adını eklemiyor. Son satırdaki ikame bir metnin ortasında duruyor ve sonucu dosya adının parçası oluyor. Aynı biçimlendirmeyi alt kabuk açmadan yapmak isterseniz printf -v rapor 'rapor_%03d.txt' "$hata_sayisi" yazabilirsiniz; printf -v sonucu doğrudan değişkene atar, ayrıntılar echo ve printf kullanımı yazısında. grep ve wc gibi araçların kendisi için Linux sunucu komutları yazısına bakabilirsiniz.
$( ), ters tırnak ve benzer yazımlar

| Yazım | Ne yapar | Not |
|---|---|---|
| $(komut) | Çıktıyı alt kabukta yakalar | Önerilen biçim, iç içe kullanımı kolay |
| `komut` | Aynı işi yapar | Eskimiş; ters eğik çizgi kuralları farklı |
| $(< dosya) | Dosya içeriğini okur | $(cat dosya) ile eşdeğer, daha hızlı |
| $(( ifade )) | Aritmetik genişletme | Komut çalıştırmaz, sayı hesaplar |
| <(komut) | Süreç ikamesi | Çıktıyı dosya adı gibi verir |
| ${ komut; } | Alt kabuksuz ikame | Kılavuz 5.3 anlatıyor; 5.2.37 ile çalışmadı |
Aritmetik genişletme Bash aritmetik işlemler, süreç ikamesi Bash process substitution yazısında ayrıca anlatılıyor.
Sık yapılan hatalar
- İkameyi tırnaksız kullanmak: boşluklar ve satırlar bölünür, çıktıdaki
*dosya adlarına genişler. for satir in $(cat dosya)ile satır okumaya çalışmak: döngü satırları değil sözcükleri dolaşır.local x=$(komut)ya daexport x=$(komut)yazıp$?kontrol etmek: durum yerleşik komuta aittir.- İkame içindeki değişken atamasının dışarıda kalmasını beklemek: alt kabuk değişiklikleri ana kabuğa dönmez.
- Sondaki yeni satırların korunduğunu varsaymak: hepsi silinir; gerekirse sona bir karakter ekleyip sonra silin.
set -eaçıkken ikame içindeki ara hataların betiği durduracağını sanmak:inherit_errexitkapalıysa durdurmaz.
Sık Sorulan Sorular
$( ) ile ters tırnak arasında hız farkı var mı?
İkisi de komutu bir alt kabukta çalıştırır; kılavuz aralarında bir hız farkından söz etmez. Fark sözdizimindedir: $( ) iç içe yazılabilir ve içindeki karakterler özel işlenmez. Kılavuzun hız konusunda söylediği tek şey, $(< dosya) biçiminin $(cat dosya) biçiminden daha hızlı olduğudur.
Komutun hem çıktısını hem çıkış kodunu nasıl alırım?
Atamayı tek başına bir satıra yazın ve hemen ardından $? değerini saklayın: cikti=$(komut); kod=$?. Ya da if cikti=$(komut); then ... fi biçimini kullanın.
Standart hata çıktısı da yakalanır mı?
Hayır, yalnızca standart çıktı yakalanır. Hata mesajlarını da almak için ikamenin içinde yönlendirme yapın: cikti=$(komut 2>&1). Yönlendirme kuralları stdout ve stderr yönlendirme yazısında.
İkame içinde fonksiyonlarımı kullanabilir miyim?
Evet. Alt kabuk, ana kabuğun bir kopyası olduğu için o ana kadar tanımlanmış fonksiyonları ve değişkenleri görür; yalnızca değişiklikler geri dönmez. Fonksiyonlardan değer döndürme yolları Bash fonksiyon tanımlama yazısında.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Bash öğrenme rehberi · Fonksiyonlar · Subshell · Process substitution · set -euo pipefail · Tırnak kuralları