Bash'te metin işlemlerinin çoğu harici bir program çağırmadan, parametre genişletmeyle yapılır: ${#s} uzunluğu, ${s:0:4} alt metni verir; ${s#desen} ve ${s##desen} baştan, ${s%desen} ve ${s%%desen} sondan eşleşen kısmı siler; ${s/eski/yeni} ilk eşleşmeyi, ${s//eski/yeni} bütün eşleşmeleri değiştirir. ${s^^} büyük harfe, ${s,,} küçük harfe çevirir, ${s@Q} gibi dönüşümler ise değeri yeniden kullanılabilir biçimde yazar.
Bu yazıda bu genişletmelerin her birini kılavuzdaki kurallara göre, kısa ve en uzun eşleşme farkıyla, dizilere uygulanışıyla ve Türkçe karakterlerdeki davranışıyla anlatıyoruz. Varsayılan değer operatörleri (:-, :=, :?, :+) ayrı bir yazının konusu: Bash parametre genişletme ve varsayılan değer. Serinin tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz. Örnekler GNU Bash 5.2.37 ile LC_ALL=C.UTF-8 ortamında çalıştırıldı; neden önemli olduğunu ilk bölümde göreceksiniz.
Uzunluk: ${#s} ve yerel ayar

${#s} değişkenin değerinin karakter cinsinden uzunluğunu verir. Kılavuz "karakter" der ve bu ayrım Türkçe metinlerde önemlidir: ğ, ş, İ gibi harfler UTF-8'de birden fazla bayt tutar. Bash'in bunları tek karakter sayması için kabuğun bir UTF-8 yerel ayarıyla (locale) çalışması gerekir.
s="ğüşİı Çöz"
echo "${#s}" # çıktı: 9
bos=
echo "${#bos}" # çıktı: 0Bu yazının denendiği ortamda LC_ALL ayarlanmadan aynı satır 9 yerine 16 sonucunu verdi, yani baytları saydı. Betiğinizin hangi yerel ayarla çalışacağı, onu başlatan ortama bağlıdır; etkileşimli terminalinizde doğru sonuç almanız, aynı betiğin başka bir ortamda da aynı sonucu vereceği anlamına gelmez. Türkçe metinle çalışan betiğin başına, sisteminizde bulunan bir UTF-8 yerel ayarını seçen export LC_ALL=C.UTF-8 gibi bir satır eklemek bu belirsizliği azaltır. Dizilerde ${#dizi[@]} eleman sayısını verir; o kullanım Bash dizi kullanımı yazısında.
Alt metin: ${s:ofset:uzunluk}
${s:ofset} metnin ofset konumundan sonuna kadarki kısmını, ${s:ofset:uzunluk} ise o konumdan başlayan en fazla uzunluk karakteri verir. Sayma sıfırdan başlar. Kılavuza göre ofset ve uzunluk aritmetik ifadedir; bu yüzden değişken adını $ olmadan da yazabilirsiniz. Negatif ofset sondan sayar, negatif uzunluk ise karakter sayısı değil, sondan sayılan bir bitiş konumu olarak yorumlanır:
tarih=2026-11-07
echo "${tarih:0:4}" # çıktı: 2026
echo "${tarih:5:2}" # çıktı: 11
echo "${tarih:5}" # çıktı: 11-07
echo "${tarih: -2}" # çıktı: 07
echo "${tarih:2:-3}" # çıktı: 26-11
i=8
echo "${tarih:i}" # çıktı: 07
echo "[${tarih:20}]" # çıktı: []${tarih:2:-3} ikinci karakterden başlayıp sondan üç karakter öncesine kadar gidiyor. Ofset metnin uzunluğunu aştığında sonuç boş metindir, hata verilmez. Negatif ofseti iki noktadan bir boşlukla ayırmayı unutmayın: ${s:-2} yazarsanız Bash bunu varsayılan değer operatörü olarak okur. Konumsal parametrelerde ${@:2} ikinci argümandan itibaren bütün argümanları verir; burada sayma 1'den başlar ve ofset 0 olursa listeye $0 da eklenir. İndeksli dizilerde ${dizi[@]:1:2} ikinci elemandan başlayan iki elemanı verir; bu durumda negatif uzunluk kılavuza göre bir genişletme hatasıdır. Kılavuz ayrıca alt metin genişletmesinin ilişkisel dizilere uygulanmasının tanımsız sonuç ürettiğini belirtir, yani anahtar sırasına güvenmeye çalışmayın.
Desenle baştan ve sondan silmek: #, ##, %, %%
Bu dört operatör bir deseni değerin başına (#) ya da sonuna (%) karşı eşleştirir ve eşleşen kısmı siler. Tek karakterli biçim en kısa eşleşmeyi, çift karakterli biçim en uzun eşleşmeyi siler. Desen düzenli ifade değil, dosya adlarında kullanılan joker desenidir: * herhangi bir metin, ? tek karakter, [...] karakter kümesi anlamına gelir. Dosya yollarıyla çalışırken bu operatörler basename ve dirname gibi harici komutların yerini tutar:
yol=/var/www/site/index.html.bak
echo "${yol#*/}" # çıktı: var/www/site/index.html.bak
echo "${yol##*/}" # çıktı: index.html.bak
echo "${yol%/*}" # çıktı: /var/www/site
echo "${yol%.*}" # çıktı: /var/www/site/index.html
echo "${yol%%.*}" # çıktı: /var/www/site/index
dosya=${yol##*/}
echo "${dosya#*.}" # çıktı: html.bak
echo "${dosya##*.}" # çıktı: bakÖrneği satır satır okuyalım. ${yol#*/} baştan ilk / dahil en kısa kısmı sildi; ${yol##*/} son / dahil en uzun kısmı silip dosya adını bıraktı. ${yol%/*} sondan son / ile sonrasını silerek dizini verdi. ${yol%.*} yalnızca son uzantıyı, ${yol%%.*} ise ilk noktadan sonrasını tamamen sildi. Uzantı almak için ${dosya##*.} en sağdaki uzantıyı verir. Kısaca: # baştan, % sondan siler; işaret iki kez yazılırsa açgözlü davranır.
Desenin bir değişkenden gelmesi gerektiğinde tırnak kritik hale gelir. Kılavuzun desen eşleştirme kurallarına göre tırnak içindeki karakterler kendi anlamlarıyla eşleşir. Desen değişkenini tırnaksız yazarsanız içindeki * joker olarak yorumlanır, tırnak içinde yazarsanız düz bir yıldız karakteri olarak aranır:
desen='a*b'
s='a*b-axb'
echo "[${s##$desen}]" # çıktı: []
echo "[${s##"$desen"}]" # çıktı: [-axb]Kullanıcıdan ya da bir dosyadan gelen bir metni silmek istiyorsanız her zaman ${s#"$onek"} biçiminde tırnaklayın. Joker desenlerin tam listesi ve karakter sınıfları Bash joker karakterler, +(...) gibi genişletilmiş desenler Bash extglob yazısında.
Bul ve değiştir: /, //, /#, /%

${s/desen/yeni} desenin ilk eşleşmesini yeni ile değiştirir. Kılavuz eşleşmenin en uzun eşleşme olduğunu belirtir. İki eğik çizgiyle (//) bütün eşleşmeler değişir. Desenin başına # koyarsanız yalnızca değerin başında, % koyarsanız yalnızca sonunda eşleşme aranır. Yeni metin boşsa eşleşmeler silinir ve son eğik çizgi yazılmayabilir:
s="elma armut elma kiraz"
echo "${s/elma/ayva}" # çıktı: ayva armut elma kiraz
echo "${s//elma/ayva}" # çıktı: ayva armut ayva kiraz
echo "${s/#armut/ayva}" # çıktı: elma armut elma kiraz
echo "${s/#elma/ayva}" # çıktı: ayva armut elma kiraz
echo "${s/%kiraz/vişne}" # çıktı: elma armut elma vişne
echo "[${s//elma}]" # çıktı: [ armut kiraz]
echo "${s// /_}" # çıktı: elma_armut_elma_kirazÜçüncü satırda armut metnin başında olmadığı için hiçbir şey değişmedi. ${s//elma} iki elma sözcüğünü de sildi ama etraftaki boşluklar kaldı; köşeli parantezler bunu görünür kılıyor. Kılavuza göre nocasematch seçeneği açıkken bu eşleşme büyük ve küçük harf ayrımı yapmadan yapılır.
Yeni metindeki & karakteri özel bir anlam taşır. Kılavuz, varsayılan olarak açık olan patsub_replacement seçeneği etkinken yeni metindeki tırnaksız & karakterinin eşleşen metinle değiştirildiğini anlatır. Düz bir & istiyorsanız ters eğik çizgiyle ya da tırnakla kaçırırsınız. Asıl tuzak, yeni metin bir değişkenden geldiğinde ortaya çıkar:
s="fiyat: 100 TL"
echo "${s/100/[&]}" # çıktı: fiyat: [100] TL
echo "${s/100/\&}" # çıktı: fiyat: & TL
r='A&B'
echo "${s/fiyat/$r}" # çıktı: AfiyatB: 100 TL
echo "${s/fiyat/"$r"}" # çıktı: A&B: 100 TLr değişkeninin değeri A&B olduğu halde tırnaksız kullanıldığında & eşleşen fiyat sözcüğüyle değişti ve AfiyatB çıktı. Değişkeni yeni metin kısmında tırnaklamak sorunu çözüyor. Kullanıcı girdisini değiştirme metni olarak kullanıyorsanız bu tırnağı unutmayın.
Büyük ve küçük harf: ^, ^^, ,, ve ,
${s^} ilk karakteri büyük harfe, ${s^^} bütün karakterleri büyük harfe çevirir; ${s,} ve ${s,,} aynı işi küçük harf için yapar. Operatörden sonra bir desen yazarsanız yalnızca o desene uyan karakterler değişir; kılavuz desenin tek bir karakteri eşleştirecek biçimde yazılmasını önerir. Türkçe metinde sonuç yerel ayara bağlıdır:
s="istanbul ılık"
echo "${s^}" # çıktı: Istanbul ılık
echo "${s^^}" # çıktı: ISTANBUL ILIK
echo "${s^^[aeiu]}" # çıktı: IstAnbUl ılık
t="İZMİR"
echo "${t,,}" # çıktı: izmir
echo "${t,}" # çıktı: iZMİRÇıktılar Türkçe yazım kuralına uymuyor: istanbul büyütülünce İstanbul yerine Istanbul oldu, ılık sözcüğündeki ı harfi I oldu ve İZMİR küçültülünce izmir çıktı. Bunun nedeni C.UTF-8 yerel ayarının Türkçe değil, dilden bağımsız bir yerel ayar olmasıdır. Türkçe harf eşlemesi için sistemde Türkçe bir yerel ayarın kurulu ve etkin olması gerekir; bunu kendi sisteminizde ayrıca denemeniz gerekir. Kullanıcıya gösterilecek Türkçe metinlerde bu dönüşümlere güvenmeden önce sonucu kontrol edin.
Dönüşüm operatörleri: ${s@op}
${s@op} biçimi, kılavuzun tek harfli operatörler olarak listelediği dönüşümleri uygular. U, u ve L harf dönüşümü yapar; Q değeri kabuğa yeniden girdi olarak verilebilecek biçimde tırnaklar; E ters eğik çizgi kaçışlarını $'...' tırnaklaması gibi açar; A değişkeni nitelikleriyle yeniden oluşturacak bir atama ya da declare komutu üretir; a niteliklerin harflerini verir:
s=$'iki\nsatır'
echo "${s@Q}" # çıktı: $'iki\nsatır'
k="O'nun dosyası"
echo "${k@Q}" # çıktı: 'O'\''nun dosyası'
e='a\tb'
echo "${e@E}" # çıktı: a b
ad=ayşe
echo "${ad@u} ${ad@U}" # çıktı: Ayşe AYŞE
declare -i n=5
echo "${n@a}" # çıktı: i
echo "${n@A}" # çıktı: declare -i n='5' @Q özellikle günlük mesajlarında ve hata ayıklamada değerlidir: değerdeki yeni satır, sekme ya da tek tırnak açıkça görünür ve sonucu kopyalayıp kabuğa yapıştırdığınızda aynı değeri elde edersiniz. @E satırındaki çıktıda a ile b arasında gerçek bir sekme karakteri var. Kılavuz ayrıca P (değeri istem metni gibi genişletme) ile dizilerde anahtar ve değer çiftleri üreten K ve k operatörlerini de listeler. İstem kaçışları için Bash PS1 prompt, tırnaklama kuralları için tek tırnak ve çift tırnak farkı yazısına bakın.
Dizilere ve argümanlara toplu uygulamak
Kılavuz bu yazıdaki silme, değiştirme, harf ve dönüşüm operatörlerinin @ ya da * ile kullanılan dizilere ve konumsal parametrelere eleman eleman uygulandığını söyler. Bu, bir döngü yazmadan bütün elemanları dönüştürmenizi sağlar:
dosyalar=(rapor.txt not.txt liste.txt)
echo "${dosyalar[@]%.txt}" # çıktı: rapor not liste
echo "${dosyalar[@]/#/yedek_}" # çıktı: yedek_rapor.txt yedek_not.txt yedek_liste.txt
echo "${dosyalar[@]^}" # çıktı: Rapor.txt Not.txt Liste.txt
set -- a.log b.log
echo "${@%.log}" # çıktı: a b${dosyalar[@]/#/yedek_} boş bir deseni her elemanın başında eşleştirerek önek ekliyor; aynı yolla ${dizi[@]/%/.bak} sonek ekler. Sonucu yeni bir diziye almak için yeni=("${dosyalar[@]%.txt}") yazabilirsiniz; çift tırnak her elemanın ayrı kalmasını sağlar.
Baştaki ve sondaki boşlukları silmek
Bash'te hazır bir trim işlevi yoktur, ama silme operatörleri ve karakter sınıflarıyla kurulabilir. Fikir şudur: önce boşluk olmayan ilk karakterden itibaren her şeyi silerek baştaki boşlukları bir değişkende yakalarız, sonra o değişkeni tırnak içinde baştan sileriz. Aynısını sondan yaparız:
s=" merhaba dünya "
bas=${s%%[![:space:]]*} # baştaki boşluklar
s=${s#"$bas"}
son=${s##*[![:space:]]} # sondaki boşluklar
s=${s%"$son"}
echo "[$s]" # çıktı: [merhaba dünya][![:space:]] boşluk olmayan herhangi bir karakter demektir; ${s%%[![:space:]]*} boşluk olmayan ilk karakterden sonrasının hepsini silip yalnızca baştaki boşlukları bırakır. İkinci adımdaki tırnak, yakalanan boşlukların düz metin olarak aranmasını sağlıyor. Ortadaki boşluk korunuyor.
Uygulamalı örnek: dosya uzantılarını toplu değiştirmek
Bir dizindeki .jpeg uzantılı dosyaları .jpg olarak yeniden adlandırmak, sondan silme operatörünün tipik kullanımıdır. Örnek, yalnızca kendi geçici dizininde dosya oluşturuyor:
touch tatil.jpeg kapak.jpeg not.txt
for f in *.jpeg; do
mv -- "$f" "${f%.jpeg}.jpg"
done
printf '%s\n' *
# çıktı:
# kapak.jpg
# not.txt
# ornek.sh
# tatil.jpg${f%.jpeg} uzantıyı siliyor, arkasına yeni uzantıyı ekliyoruz. mv -- yazımı, adı - ile başlayan bir dosyanın seçenek sanılmasını önler. Eşleşen dosya yoksa *.jpeg deseni olduğu gibi kalır ve mv hata verir; bunu önlemek için shopt -s nullglob kullanılabilir. Dosya listeleme ve mv gibi komutların kendisi bu serinin değil, Linux sunucu komutları yazısının konusu.
Hızlı başvuru tablosu

| Yazım | Ne yapar | Örnek sonuç (s=dosya.tar.gz) |
|---|---|---|
| ${#s} | Karakter sayısı | 12 |
| ${s:0:5} | Alt metin | dosya |
| ${s#*.} | Baştan en kısa eşleşmeyi siler | tar.gz |
| ${s##*.} | Baştan en uzun eşleşmeyi siler | gz |
| ${s%.*} | Sondan en kısa eşleşmeyi siler | dosya.tar |
| ${s%%.*} | Sondan en uzun eşleşmeyi siler | dosya |
| ${s/./_} | İlk eşleşmeyi değiştirir | dosya_tar.gz |
| ${s//./_} | Bütün eşleşmeleri değiştirir | dosya_tar_gz |
| ${s^^} | Hepsini büyük harf yapar | DOSYA.TAR.GZ |
| ${s@Q} | Yeniden kullanılabilir tırnaklama | 'dosya.tar.gz' |
Tablodaki sonuçları ayrıca denedik:
s=dosya.tar.gz
echo "${#s} ${s:0:5} ${s#*.} ${s##*.} ${s%.*} ${s%%.*}"
# çıktı: 12 dosya tar.gz gz dosya.tar dosya
echo "${s/./_} ${s//./_} ${s^^} ${s@Q}"
# çıktı: dosya_tar.gz dosya_tar_gz DOSYA.TAR.GZ 'dosya.tar.gz' Sık yapılan hatalar
- Deseni düzenli ifade sanmak:
${s#[0-9]+}"bir veya daha fazla rakam" değil, bir rakam ve ardından gelen+karakteri anlamına gelir. - Tek ve çift operatörü karıştırmak:
%en kısa,%%en uzun eşleşmeyi siler; çok uzantılı dosyalarda fark büyüktür. - Değişkenden gelen deseni tırnaklamamak: içindeki
*ve?joker olarak çalışır. - Değiştirme metnindeki
&karakterini unutmak: tırnaksız bir değişken içindeyse eşleşen metinle yer değiştirir. - Türkçe harf dönüşümüne güvenmek:
C.UTF-8yerel ayarındaibüyütülünceIolur. - Yerel ayarı kontrol etmemek: UTF-8 dışı ortamda
${#s}bayt sayar ve alt metin işlemleri harfleri bölebilir.
Sık Sorulan Sorular
sed veya awk yerine bunları kullanmak daha mı iyi?
Tek bir değişken üzerinde küçük bir işlem için parametre genişletme yeni bir süreç başlatmadığı için yeterli ve pratiktir. Büyük dosyaları satır satır işlemek ya da düzenli ifade gerektiren dönüşümler için sed ve awk gibi araçlar daha uygundur; bu araçlar Bash kılavuzunun kapsamı dışındadır.
Bash'te düzenli ifadeyle eşleşme yapılabilir mi?
Evet, [[ $s =~ desen ]] genişletilmiş düzenli ifade kullanır ve yakalanan grupları BASH_REMATCH dizisine yazar. Ayrıntılar Bash regex ve BASH_REMATCH yazısında.
Bir metin başka bir metni içeriyor mu, nasıl kontrol ederim?
En kısa yol [[ $s == *"$aranan"* ]] karşılaştırmasıdır; aranan metni tırnaklamak içindeki joker karakterlerin düz okunmasını sağlar. Koşul ifadeleri test ve [[ ]] farkı yazısında.
Bu işlemleri bir fonksiyonun çıktısına doğrudan uygulayabilir miyim?
Parametre genişletme yalnızca değişkenler üzerinde çalışır. Önce çıktıyı bir değişkene alın (s=$(komut)), sonra genişletmeyi uygulayın. Komut ikamesinin ayrıntıları Bash komut ikamesi 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 · Varsayılan değer · Joker karakterler · extglob · Regex ve BASH_REMATCH · Diziler