Bash'te süslü parantez genişletmesi (brace expansion), ortak bir önek ve sonekle birden fazla metin üretir: echo dosya{1,2,3}.txt komutu dosya1.txt dosya2.txt dosya3.txt yazar, {1..10} ise 1'den 10'a kadar sayıları, {a..e} harfleri üretir. Tilde genişletmesi ise sözcüğün başındaki ~ karakterini ev dizininizle ($HOME) değiştirir; ~+ o anki dizini, ~- bir önceki dizini verir. İkisi de Bash'in komut satırını çalıştırmadan önce uyguladığı genişletmelerin ilk adımlarıdır.

Bu yazıda virgüllü listeleri, sayı ve harf dizilerini, sıfırla doldurmayı, iç içe kullanımı, genişletmenin hangi durumlarda yapılmadığını ve {1..$n} yazımının neden çalışmadığını; ardından tilde genişletmesinin bütün biçimlerini ve atamalarda nasıl davrandığını GNU Bash kılavuzuna dayanarak anlatıyoruz. Son olarak Bash'in genişletmeleri hangi sırayla yaptığını bir tabloyla özetliyoruz. Serinin tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz. Örnekler GNU Bash 5.2.37 ile çalıştırıldı.

Virgüllü liste: önek, seçenekler, sonek

Virgüllü liste: önek, seçenekler, sonek

Kılavuz süslü parantez genişletmesini üç parçadan oluşan bir kalıp olarak tanımlar: isteğe bağlı bir önek (preamble), süslü parantez içinde virgülle ayrılmış metinler ya da bir dizi ifadesi, ve isteğe bağlı bir sonek (postscript). Önek parantez içindeki her metnin önüne, sonek her sonucun arkasına eklenir. Sonuçlar sıralanmaz, soldan sağa yazıldığı sırada kalır:

bash
echo a{d,c,b}e                 # çıktı: ade ace abe
echo yedek_{ocak,şubat,mart}.tar
# çıktı: yedek_ocak.tar yedek_şubat.tar yedek_mart.tar
echo {x,y}{1,2}                # çıktı: x1 x2 y1 y2
echo ayar.conf{,.bak}          # çıktı: ayar.conf ayar.conf.bak
echo {a,b{1,2},c}              # çıktı: a b1 b2 c

İlk satır kılavuzdaki örneğin aynısıdır ve sıralamanın korunduğunu gösteriyor: d, c, b sırası değişmedi. Yan yana iki süslü parantez her olasılığı üretir, yani {x,y}{1,2} dört sonuç verir. ayar.conf{,.bak} kalıbındaki boş seçenek önekin kendisini üretir; bu, bir dosyanın yedeğini almak için cp ayar.conf{,.bak} biçiminde sık kullanılır. Son satır iç içe genişletmeyi gösteriyor: kılavuza göre süslü parantez genişletmeleri iç içe yazılabilir.

Kılavuzun vurguladığı önemli bir nokta, bu genişletmenin dosya adı genişletmesine benzemekle birlikte üretilen adların var olmasının gerekmemesidir. *.txt yalnızca dizinde bulunan dosyalara genişler, {a,b}.txt ise dosyalar olsun olmasın iki metin üretir. Bu yüzden yeni dosya ya da dizin oluştururken süslü parantez, var olanları seçerken joker karakterler kullanılır. Joker desenlerin ayrıntısı Bash joker karakterler yazısında.

Dizi ifadeleri: {x..y} ve {x..y..artış}

Virgüllü liste yerine x..y biçiminde bir dizi ifadesi yazabilirsiniz. x ve y tamsayı ya da tek harf olabilir, ama ikisi aynı türde olmalıdır. Üçüncü bir ..artış verilirse her terim arasındaki fark o olur; verilmezse artış duruma göre 1 ya da -1'dir. Kılavuza göre sınırlardan biri sıfırla başlıyorsa üretilen bütün terimler aynı sayıda basamağa sıfırla doldurulur. Harfler C yerel ayarındaki sıraya göre üretilir:

bash
echo {1..5}          # çıktı: 1 2 3 4 5
echo {5..1}          # çıktı: 5 4 3 2 1
echo {0..20..5}      # çıktı: 0 5 10 15 20
echo {01..10..3}     # çıktı: 01 04 07 10
echo {a..e}          # çıktı: a b c d e
echo {z..t..2}       # çıktı: z x v t
echo {1..a}          # çıktı: {1..a}

{5..1} geriye doğru saydı, çünkü varsayılan artış -1 oldu. {01..10..3} sıfırla doldurmanın ve artışın birlikte kullanımını gösteriyor; tarih ya da sıra numarası içeren dosya adları üretirken gun_{01..31}.log gibi kalıplar sıralı listelemeyi kolaylaştırır. {1..a} satırında tür karışık olduğu için ifade geçersiz ve kılavuzun dediği gibi olduğu gibi bırakıldı. Kılavuz harf dizilerinin C yerel ayarına göre üretildiğini söylediği için {a..z} içinde ç, ğ, ş gibi Türkçe harfler bulunmaz.

Dizi ifadesini sayılar üzerinde döngü kurmak için de kullanabilirsiniz: for i in {1..5}. Ancak üst sınır bir değişkendeyse bu yazım çalışmaz; aşağıda nedenini göreceğiz. Döngü türleri Bash for döngüsü yazısında ayrıntılı anlatılıyor.

Pratik kullanım: dizin ağacı oluşturmak

Süslü parantez genişletmesinin en bilinen kullanımı, tek komutla birden fazla dizin oluşturmaktır. Kılavuzdaki mkdir örneğine benzer şekilde, iki genişletmeyi birleştirerek dört dizin oluşturalım. Örnek yalnızca kendi geçici dizininde çalışıyor:

bash
mkdir -p proje/{src,test}/{v1,v2}
printf '%s\n' proje/*/*
# çıktı:
#   proje/src/v1
#   proje/src/v2
#   proje/test/v1
#   proje/test/v2

mkdir -p proje/{src,test}/{v1,v2} komutu Bash tarafından önce dört ayrı argümana açılıyor, mkdir ise süslü parantezden habersiz bu dört yolu alıyor. Komutu çalıştırmadan önce neye genişleyeceğini görmek isterseniz başına echo koymak iyi bir alışkanlıktır: echo mkdir -p proje/{src,test}/{v1,v2}. Özellikle silme ve taşıma komutlarında bu küçük kontrol, yanlış bir kalıbın zarar vermesini önler.

Genişletmenin yapılmadığı durumlar

Genişletmenin yapılmadığı durumlar

Kılavuza göre doğru biçimli bir süslü parantez genişletmesi tırnaksız açılış ve kapanış parantezleri ile en az bir tırnaksız virgül ya da geçerli bir dizi ifadesi içermelidir. Bu koşulu sağlamayan kalıplar değiştirilmeden bırakılır. Virgül veya parantez ters eğik çizgiyle kaçırılabilir. Ayrıca parametre genişletmesiyle çakışmayı önlemek için ${ dizisi süslü parantez genişletmesine uygun sayılmaz:

bash
echo {tek}            # çıktı: {tek}
echo "{a,b}"          # çıktı: {a,b}
echo \{a,b}           # çıktı: {a,b}
echo {a\,b,c}         # çıktı: a,b c
echo {a, b}           # çıktı: {a, b}
ad=dosya
echo ${ad}{1,2}       # çıktı: dosya1 dosya2

{tek} virgül içermediği için olduğu gibi kaldı. Tırnak içindeki ve kaçırılmış parantezler genişlemedi. {a\,b,c} kalıbında ilk virgül kaçırıldığı için seçenekler a,b ve c oldu. {a, b} satırı ince bir tuzaktır: tırnaksız boşluk, kabuğun bunu baştan iki ayrı sözcük olarak okumasına neden olur ve hiçbir sözcükte tam bir kalıp kalmaz. Son satırda ${ad} bir parametre genişletmesi olarak korunuyor ve arkasındaki {1,2} ayrıca genişliyor.

Neden {1..$n} çalışmaz: genişletme sırası

Kılavuz süslü parantez genişletmesinin diğer bütün genişletmelerden önce yapıldığını ve tamamen metinsel olduğunu söyler. Bash parantezlerin arasındaki metne hiçbir sözdizimsel anlam yüklemez; diğer genişletmeler için özel olan karakterler sonuçta korunur. Bu, değişken içeren bir dizi ifadesinin neden çalışmadığını açıklar: süslü parantez genişletmesi yapılırken $n henüz 3 değil, sadece $n metnidir ve {1..$n} geçerli bir dizi ifadesi değildir:

bash
n=3
echo {1..$n}                       # çıktı: {1..3}
for (( i = 1; i <= n; i++ )); do printf '%s ' "$i"; done; echo   # çıktı: 1 2 3
meyve=elma
echo {$meyve,armut}                # çıktı: elma armut

Değişken sınırlı bir sayı dizisi için C tarzı for (( )) döngüsü doğru araçtır. Aynı kural ters yönde bir avantaj sağlar: virgüllü listede bir değişken kullanırsanız süslü parantez önce $meyve ve armut metinlerini üretir, ardından parametre genişletmesi $meyve değerini yerine koyar. Bu yüzden son satır elma armut yazıyor. Dizi ifadesini değişkenle kurmak için eval kullanan örneklerle karşılaşabilirsiniz; eval girdiyi kod olarak çalıştırdığı için risklidir, ayrıntılar Bash eval yazısında.

Süslü parantez ve joker karakter farkı, set +B

Aşağıdaki örnek boş bir dizinde çalışıyor. Süslü parantez var olmayan iki dosya adını üretiyor, joker desen ise eşleşen dosya bulamadığı için olduğu gibi kalıyor. Kılavuz, eski sh kabuklarının süslü parantezlere özel anlam vermediğini ve sıkı sh uyumluluğu için Bash'in +B seçeneğiyle başlatılabileceğini ya da genişletmenin set +B ile kapatılabileceğini söyler:

bash
echo yok_{1,2}.txt     # çıktı: yok_1.txt yok_2.txt
echo yok_?.txt         # çıktı: yok_?.txt
set +B
echo {a,b}             # çıktı: {a,b}
set -B
echo {a,b}             # çıktı: a b

set +B sonrası {a,b} düz metin olarak kaldı, set -B ile genişletme yeniden açıldı. Bu seçenek varsayılan olarak açıktır; kapatmanız gereken durum, süslü parantez içeren metinleri tırnaksız işleyen eski bir betiği çalıştırmak gibi nadir senaryolardır. POSIX uyumluluğu konusunu Bash ve sh farkı yazısında anlatıyoruz.

Tilde genişletmesi: ~, ~/yol ve ~kullanici

Kılavuza göre bir sözcük tırnaksız bir ~ ile başlıyorsa, ilk tırnaksız eğik çizgiye kadar olan kısım (eğik çizgi yoksa sözcüğün tamamı) tilde öneki sayılır. Tildeden sonraki karakterler bir kullanıcı adı olarak yorumlanır. Ad boşsa tilde HOME değişkeninin değeriyle değiştirilir; doluysa o kullanıcının ev dizini kullanılır. Kullanıcı adı geçersizse önek değiştirilmeden bırakılır. Örnekte kendi ev dizinimizi göstermemek için HOME değişkenini betik içinde geçici bir değere ayarladık:

bash
HOME=/home/ornek
echo ~                  # çıktı: /home/ornek
echo ~/belgeler         # çıktı: /home/ornek/belgeler
echo "~/belgeler"       # çıktı: ~/belgeler
echo "$HOME/belgeler"   # çıktı: /home/ornek/belgeler
echo ~yok_boyle_biri/x  # çıktı: ~yok_boyle_biri/x
yol=~/bin:~/araclar
echo "$yol"             # çıktı: /home/ornek/bin:/home/ornek/araclar
echo ayar=~/x           # çıktı: ayar=/home/ornek/x
d='~/x'
echo $d                 # çıktı: ~/x

Çift tırnak içindeki ~ genişlemedi; tırnak içinde ev dizini gerekiyorsa "$HOME/belgeler" yazmalısınız. Bu, betiklerde en sık yapılan hatalardan biridir: "~/yedek" yazan bir betik ev dizinine değil, bulunduğu dizinde ~ adlı bir klasöre yazmaya çalışır. Var olmayan kullanıcı adı içeren önek değişmeden kaldı. Kılavuz ayrıca tilde genişletmesinin sonucunun tırnaklanmış gibi işlendiğini, yani sözcük bölmeye ve dosya adı genişletmeye uğramadığını söyler; ev dizininin yolunda boşluk olsa bile ~ tek sözcük olarak kalır.

Atamalarla ilgili iki kural var. Kılavuza göre Bash her değişken atamasında ilk = işaretinden ve her : karakterinden hemen sonra gelen tırnaksız tilde öneklerini genişletir. Bu sayede PATH gibi iki noktayla ayrılmış listelerde ~/bin:~/araclar yazabilirsiniz. Ayrıca Bash, POSIX modunda değilken, bir komutun argümanı olarak yazılmış ve atama biçimine benzeyen sözcüklerde de tilde genişletmesi yapar; echo ayar=~/x satırı bunu gösteriyor. Son satır ise tildenin yalnızca kaynak koddaki tırnaksız bir karakter olduğunda genişlediğini gösteriyor: değişkenin değeri olarak gelen ~ genişletme sonrasında yeniden yorumlanmaz.

~+ ve ~- ile çalışma dizinleri

~+ ve ~- ile çalışma dizinleri: HOME değişkeninin değeri, ~/belgeler, ~ahmet/belgeler, ~+/x

Tilde önekinin iki özel biçimi vardır. ~+ o anki çalışma dizinini tutan PWD değişkeninin değerini, ~- ise ayarlıysa bir önceki dizini tutan OLDPWD değişkeninin değerini verir. Aşağıdaki örnek iki dizin oluşturup birinden diğerine geçiyor ve gerçek yolu ekrana basmamak için yalnızca son bileşeni yazdırıyor:

bash
mkdir -p eski yeni
cd eski
cd ../yeni
a=~-
b=~+
echo "${a##*/} ${b##*/}"           # çıktı: eski yeni
[[ ~+ == "$PWD" ]] && echo "~+ ile PWD aynı"   # çıktı: ~+ ile PWD aynı

Kılavuz bunlara ek olarak ~N, ~+N ve ~-N biçimlerini de tanımlar: tildeden sonra bir sayı gelirse önek, dizin yığınından dirs +N ya da dirs -N komutunun göstereceği elemanla değiştirilir. Başında işaret olmayan bir sayı + kabul edilir. Bu biçimler pushd ve popd ile dizin yığını kullananlar için anlamlıdır ve günlük betiklerde nadiren gerekir.

YazımNeyle değiştirilir
~HOME değişkeninin değeri
~/belgeler$HOME/belgeler
~ahmet/belgelerahmet kullanıcısının ev dizinindeki belgeler
~+/x$PWD/x
~-/xOLDPWD ayarlıysa $OLDPWD/x
~N veya ~+Ndirs +N çıktısındaki dizin
~-Ndirs -N çıktısındaki dizin
"~/x" (tırnaklı)Değişmez, düz metin kalır

Bash genişletmeleri hangi sırayla yapar?

Kılavuz, komut satırı sözcüklere ayrıldıktan sonra uygulanan genişletmeleri ve sıralarını açıkça verir. Bu sıra, bu yazıdaki birçok davranışı açıklar: süslü parantez ilk sırada olduğu için değişken değerlerini göremez; tilde, parametre, aritmetik ve komut ikamesi aynı adımda soldan sağa yapılır; sözcük bölme ve dosya adı genişletmesi bunlardan sonra gelir ve tırnak kaldırma en son yapılır:

SıraGenişletmeSözcük sayısını artırabilir mi
1Süslü parantez genişletmesiEvet
2Tilde, parametre, aritmetik genişletme ve komut ikamesi (soldan sağa)Hayır ("$@" ve diziler hariç)
3Sözcük bölmeEvet
4Dosya adı genişletmesiEvet
5Tırnak kaldırmaHayır

Kılavuza göre yalnızca süslü parantez genişletmesi, sözcük bölme ve dosya adı genişletmesi bir sözcüğü birden fazla sözcüğe çevirebilir; istisnalar "$@", $* ve dizilerin [@] ile genişletilmesidir. Destekleyen sistemlerde süreç ikamesi de ikinci adımla aynı anda yapılır. Parametre genişletmesi varsayılan değer ve string işlemleri, komut ikamesi Bash komut ikamesi, tırnakların bu sıradaki etkisi tek tırnak ve çift tırnak farkı yazısında anlatılıyor.

Sık yapılan hatalar

  • Dizi ifadesinin sınırını değişkenle vermek: {1..$n} genişlemez; for (( i=1; i<=n; i++ )) kullanın.
  • Süslü parantez içine boşluk koymak: {a, b} iki ayrı sözcüğe bölünür ve genişlemez.
  • Tek seçenekli kalıp yazmak: {tek} virgül içermediği için olduğu gibi kalır.
  • Ev dizinini tırnak içinde ~ ile yazmak: "~/yedek" genişlemez, "$HOME/yedek" yazın.
  • Tildeyi bir değişkende saklayıp sonra genişlemesini beklemek: değişkenden gelen ~ yeniden yorumlanmaz.
  • Türkçe harflerin {a..z} içinde olacağını sanmak: harf dizileri C yerel ayarına göre üretilir.

Sık Sorulan Sorular

Süslü parantez genişletmesi sh ile çalışır mı?

Kılavuz, eski sh kabuklarının bir sözcüğün içindeki süslü parantezlere özel anlam vermediğini, dosya{1,2} sözcüğünü olduğu gibi bıraktığını belirtir. Betiğinizin sh ile çalışması gerekiyorsa bu genişletmeye güvenmeyin.

Dizi ifadesiyle kaç sayı üretebilirim?

Kılavuz bir üst sınır belirtmez. Ancak genişletme komut çalışmadan önce yapıldığı için bütün terimler önce üretilir ve komuta tek seferde argüman olarak verilir. Çok büyük sayı aralıklarında, terimleri teker teker üreten C tarzı for (( )) döngüsü daha uygundur.

Tilde betikte güvenli mi, yoksa $HOME mu yazmalıyım?

Tırnaksız ve sözcük başındaysa ~ güvenle genişler. Yol tırnak içinde olacaksa ya da sözcüğün ortasında kalacaksa $HOME yazmak daha açıktır. Ev dizininin hangi başlangıç dosyalarında ayarlandığını merak ediyorsanız .bashrc ve .bash_profile farkı yazısına bakın.

Sıfırla doldurmayı printf ile de yapabilir miyim?

Evet, printf '%02d\n' 7 gibi biçim belirteçleri tek bir sayıyı doldurur. Süslü parantez ise bütün diziyi tek seferde üretir. Biçim belirteçleri echo ve printf kullanımı 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 · Joker karakterler · for döngüsü · Komut ikamesi · String işlemleri · Tırnak kuralları