Bash'te joker karakterler (glob), dosya adlarını tek tek yazmadan bir desenle seçmenizi sağlar. * herhangi bir metinle (boş metin dahil), ? tek bir karakterle, [abc] ise köşeli parantez içindeki karakterlerden biriyle eşleşir. ls *.txt yazdığınızda yıldızı ls görmez; Bash deseni önce eşleşen dosya adlarının sıralı listesine çevirir, komut bu listeyi argüman olarak alır. Eşleşme yoksa desen olduğu gibi kalır; bu davranışı nullglob ve failglob seçenekleri değiştirir.

Bu yazı kılavuzun 3.5.8 Filename Expansion ve 3.5.8.1 Pattern Matching bölümlerini anlatıyor: temel joker karakterler, karakter sınıfları, gizli dosyalar, alt klasörlere inen **, GLOBIGNORE ve en sık yapılan hatalar. @(a|b) gibi genişletilmiş desenler ayrı bir konu, onlar için extglob yazısına bakın. Serinin tamamı Bash öğrenme rehberinde.

Dosya adı genişletmesi nasıl çalışır?

Dosya adı genişletmesi nasıl çalışır?

Bash bir komut satırını çalıştırmadan önce sırayla bir dizi genişletme uygular. Dosya adı genişletmesi bunların sonuncularındandır ve kelime bölünmesinden sonra gelir. Kılavuza göre Bash her kelimede tırnaklanmamış *, ? veya [ karakteri arar; bulursa kelimeyi bir desen olarak kabul eder ve onu desene uyan dosya adlarının alfabetik sıralı listesiyle değiştirir. set -f ile bu genişletmeyi tümüyle kapatabilirsiniz.

Bu sıralamanın önemli bir sonucu var: deseni genişleten Bash'tir, komut değil. echo, cp veya rm hiçbir zaman *.txt görmez, yalnızca Bash'in ürettiği dosya adlarını görür. Bu yüzden aynı desen her komutla aynı biçimde çalışır. Tersine, deseni tırnak içine alırsanız Bash onu genişletmez ve komut yıldızı olduğu gibi alır; find -name "*.txt" gibi deseni kendisi işleyen araçlar bunu ister. Tırnaklama kuralları tek ve çift tırnak yazısında anlatılıyor.

Temel joker karakterler: *, ? ve [ ]

Aşağıdaki örnekler hep aynı klasör yapısını kullanıyor. Önce birkaç dosya oluşturuyor, sonra farklı desenlerin neye genişlediğini echo ile gösteriyoruz. echo burada yalnızca Bash'in ürettiği listeyi görmemizi sağlıyor.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo *.txt
# çıktı: rapor1.txt rapor10.txt rapor2.txt
echo rapor?.txt          # ? tam olarak bir karakter
# çıktı: rapor1.txt rapor2.txt
echo rapor[0-9].txt      # aralık: tek bir rakam
# çıktı: rapor1.txt rapor2.txt
echo rapor[!1].txt       # ! ile tersleme: 1 dışında bir karakter
# çıktı: rapor2.txt
echo */                  # sondaki / yalnızca klasörleri seçer
# çıktı: proje/

*.txt sonucunda rapor10.txt dosyasının rapor2.txt dosyasından önce geldiğine dikkat edin. Bash sonuçları sayısal değil, metin olarak sıralar; sıralama da geçerli yerel ayarın harf dizilimine (LC_COLLATE) bağlıdır. Örnekler C.UTF-8 ayarıyla çalıştırıldı, farklı bir yerel ayarda büyük ve küçük harflerin sırası değişebilir. Kılavuzun 5.3 sürümü sıralamayı denetleyen GLOBSORT değişkenini tanımlıyor; Bash 5.2.37 ile denediğimizde bu değişkenin bir etkisi olmadı.

Köşeli parantez ifadesi her zaman tek bir karakterle eşleşir. İçinde a-z gibi aralıklar, ilk karakter olarak ! veya ^ ile tersleme kullanılabilir. Kılavuzun kuralına göre - karakterini eşleştirmek için onu kümenin ilk ya da son karakteri yapmanız, ] için ise kümenin ilk karakteri yapmanız gerekir: [-_] tire veya alt çizgi, []x] kapanan köşeli parantez veya x ile eşleşir.

Karakter sınıfları: [[:alpha:]], [[:digit:]] ve diğerleri

Köşeli parantez içinde [:sınıf:] biçiminde POSIX karakter sınıfları kullanılabilir. Dış köşeli parantez ifadenin kendisine, iç köşeli parantez ise sınıf adına aittir; bu yüzden yazım [[:digit:]] şeklinde çift parantezlidir. Kılavuzun listelediği sınıflar şunlardır: alnum, alpha, ascii, blank, cntrl, digit, graph, lower, print, punct, space, upper, word ve xdigit. word sınıfı harf, rakam ve alt çizgiyle eşleşir.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo [[:upper:]]*           # büyük harfle başlayanlar
# çıktı: Notlar.md
echo rapor[[:digit:]][[:digit:]].txt
# çıktı: rapor10.txt

# sınıflar dosya adı dışında da çalışır
girdi="a1"
[[ $girdi == [[:alpha:]][[:digit:]] ]] && echo "harf + rakam"
# çıktı: harf + rakam

Sınıfların avantajı, aralıklardan farklı olarak yerel ayarın tanımına göre çalışmasıdır. Kılavuz [a-d] gibi aralıkların birçok yerel ayarda sözlük sırasına göre yorumlandığını ve örneğin büyük harfleri de kapsayabileceğini anlatır. Geleneksel davranışı istiyorsanız LC_COLLATE veya LC_ALL değişkenini C yapabilir ya da globasciiranges seçeneğini açabilirsiniz. Harf arıyorsanız [a-z] yerine [[:lower:]] yazmak niyetinizi daha açık ifade eder. Kılavuz ayrıca [=c=] eşdeğerlik sınıflarını ve [.simge.] harmanlama simgelerini de tanımlar; bunlar yerel ayarın harmanlama kurallarına bağlıdır.

Eşleşme olmazsa ne olur? nullglob ve failglob

Varsayılan davranışta hiçbir dosya desene uymazsa kelime değiştirilmeden bırakılır. echo *.jpg klasörde JPG yoksa ekrana *.jpg yazar. Bu, döngülerde sinsi bir hataya yol açar: for f in *.jpg döngüsü bir kez, f değişkeni *.jpg metniyle çalışır. İki shopt seçeneği bu davranışı değiştirir. nullglob açıkken eşleşmeyen desen tamamen kaldırılır. failglob açıkken Bash bir hata mesajı yazar ve komutu çalıştırmaz.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo *.jpg                       # varsayılan: desen olduğu gibi kalır
# çıktı: *.jpg

shopt -s nullglob
resimler=(*.jpg)
echo "bulunan: ${#resimler[@]}"
# çıktı: bulunan: 0
shopt -u nullglob

shopt -s failglob
echo *.jpg
echo "durum: $?"
# çıktı:
#   ornek.sh: line 15: no match: *.jpg
#   durum: 1
shopt -u failglob

nullglob dizilerle birlikte çok kullanışlıdır: dosyalar=(*.log) ataması eşleşme yoksa boş bir dizi üretir ve ${#dosyalar[@]} doğrudan dosya sayısını verir. Dizilerin kullanımı Bash dizi yazısında. nullglob seçeneğinin bir yan etkisi de vardır: seçenek açıkken tırnaklanmamış her köşeli parantezli kelime desen sayılır. Örneğin unset dizi[1] gibi tırnaksız bir argüman, eşleşen dosya yoksa komut satırından tamamen kaybolur ve unset hiçbir şey silmeden sessizce biter. Bu yüzden nullglob seçeneğini yalnızca ihtiyaç duyduğunuz yerde açıp sonra kapatmak iyi bir alışkanlıktır.

Seçenekleri açmadan güvenli bir döngü yazmak için klasik yöntem, her elemanın gerçekten var olup olmadığını kontrol etmektir:

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

for f in *.jpg; do
  [[ -e $f ]] || continue     # eşleşme yoksa f="*.jpg" olur, atla
  echo "işleniyor: $f"
done
echo "döngü bitti"
# çıktı: döngü bitti

for f in rapor*.txt; do
  [[ -e $f ]] || continue
  echo "işleniyor: $f"
done
# çıktı:
#   işleniyor: rapor1.txt
#   işleniyor: rapor10.txt
#   işleniyor: rapor2.txt

-e dosya testi ve diğer koşul ifadeleri test ve [[ ]] yazısında, döngünün kendisi for döngüsü yazısında anlatılıyor.

Gizli dosyalar: dotglob ve globskipdots

Gizli dosyalar: dotglob ve globskipdots

Nokta ile başlayan dosya adları Unix geleneğinde gizli sayılır ve joker karakterler onları kendiliğinden seçmez. Kılavuza göre dosya adının başındaki veya bir eğik çizgiden hemen sonraki nokta, desende açıkça yazılmalıdır. Yani * gizli dosyaları atlar, .* ise yalnızca gizli dosyaları seçer. dotglob seçeneği açılırsa gizli dosyalar da normal desen sonuçlarına katılır.

Burada . (bulunduğunuz klasör) ve .. (üst klasör) özel bir durumdur. dotglob açık olsa bile bu iki ad yalnızca nokta ile başlayan bir desenle eşleşebilir. Kılavuzun 5.3 sürümünde varsayılan olarak açık olduğu belirtilen globskipdots seçeneği ise bu iki adı hiçbir desenin seçmemesini sağlar; 5.2.37 sürümünde de bu seçenek açık geldi. Bu yüzden aşağıdaki .* deseni . ve .. adlarını vermiyor.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo .*                  # yalnızca gizli dosyalar
# çıktı: .gizli.txt
echo *.txt               # gizli dosya yok
# çıktı: rapor1.txt rapor10.txt rapor2.txt

shopt -s dotglob
echo *.txt               # gizli dosyalar da dahil
# çıktı: .gizli.txt rapor1.txt rapor10.txt rapor2.txt
shopt -u dotglob

dotglob seçeneği, bir klasördeki her şeyi başka bir yere taşırken veya kopyalarken gizli yapılandırma dosyalarını unutmamak için işe yarar. Bu işlemleri yapan araçlar için Linux sunucu komutları yazısına bakabilirsiniz.

Büyük/küçük harf duyarsız eşleşme: nocaseglob

Dosya adı genişletmesi varsayılan olarak harf büyüklüğüne duyarlıdır: *.png deseni resim.PNG dosyasını seçmez. nocaseglob seçeneği açıldığında eşleşme büyük/küçük harf ayrımı yapmadan yapılır. Bu seçenek yalnızca dosya adı genişletmesini etkiler. case ve [[ ]] içindeki desen eşleşmeleri için ayrı bir seçenek olan nocasematch kullanılır.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo *.png
# çıktı: *.png
shopt -s nocaseglob
echo *.png
# çıktı: resim.PNG
shopt -u nocaseglob

Alt klasörlere inmek: globstar ve **

Normal * eğik çizgiyle eşleşmez; kılavuzun kuralına göre dosya adlarında / karakteri desende her zaman açıkça bulunmalıdır. Bu yüzden */*.sh yalnızca bir seviye aşağıya bakar. globstar seçeneği açıldığında tek başına bir yol parçası olarak yazılmış **, sıfır veya daha fazla klasör seviyesiyle eşleşir. Sonuna eğik çizgi eklenirse yalnızca klasörleri ve alt klasörleri verir.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

echo **/*.sh            # globstar kapalı: ** tek * gibi davranır
# çıktı: proje/a.sh

shopt -s globstar
echo proje/**/*.sh
# çıktı: proje/a.sh proje/src/alt/c.sh proje/src/b.sh
echo proje/**/          # yalnızca klasörler
# çıktı: proje/ proje/src/ proje/src/alt/
shopt -u globstar

globstar küçük ve orta büyüklükteki klasör ağaçlarında find kullanmadan dosya toplamanın pratik bir yoludur. Çok büyük ağaçlarda bütün yolların komut satırına sığdırılması gerektiğini unutmayın; böyle durumlarda dosyaları akış halinde işleyen araçlar daha uygundur. Seçeneklerin genel kullanımı, kalıcı hale getirilmesi ve shopt -q ile sorgulanması shopt yazısında anlatılıyor.

GLOBIGNORE: bazı eşleşmeleri dışarıda bırakmak

GLOBIGNORE değişkeni iki nokta üst üste ile ayrılmış desenlerden oluşan bir listedir. Bir dosya adı joker desene uyup bu listedeki desenlerden birine de uyuyorsa sonuçlardan çıkarılır. Kılavuz bu değişkenle ilgili şaşırtıcı bir yan etkiyi belirtir: GLOBIGNORE boş olmayan bir değere ayarlandığında dotglob da açılmış gibi davranılır, yani gizli dosyalar sonuçlara girer. Eski davranışı korumak için listeye .* desenini eklemeniz gerekir.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

GLOBIGNORE='rapor1*'
echo *.txt               # rapor1 ile başlayanlar çıkarıldı, gizli dosya girdi
# çıktı: .gizli.txt rapor2.txt

GLOBIGNORE='rapor1*:.*'
echo *.txt
# çıktı: rapor2.txt
unset GLOBIGNORE

Desenler dosya adı dışında da kullanılır

Aynı desen dili Bash'in başka yerlerinde de geçerlidir, ama orada dosya sistemiyle hiçbir ilgisi yoktur. case komutundaki desenler, [[ metin == desen ]] karşılaştırması ve ${ad#desen} gibi parametre genişletmeleri metni doğrudan desenle karşılaştırır. Bu bağlamlarda nokta ve eğik çizgi özel değildir; kılavuzun ifadesiyle nokta yalnızca dosya adı eşleştirirken özel anlam taşır.

bash
dosya="arsiv/2024/rapor.tar.gz"

[[ $dosya == *.tar.gz ]] && echo "sıkıştırılmış arşiv"
# çıktı: sıkıştırılmış arşiv
[[ $dosya == arsiv/* ]] && echo "* burada / ile de eşleşir"
# çıktı: * burada / ile de eşleşir

echo "${dosya##*/}"        # en uzun */ önekini sil: dosya adı
# çıktı: rapor.tar.gz

desen="*.gz"
[[ $dosya == "$desen" ]] || echo "tırnaklı desen düz metin gibi karşılaştırılır"
# çıktı: tırnaklı desen düz metin gibi karşılaştırılır

Son örnek önemli bir kuralı gösteriyor: [[ ]] içinde desenin herhangi bir kısmını tırnaklarsanız o kısım düz metin olarak karşılaştırılır. Deseni bir değişkende tutuyorsanız sağ tarafta tırnaksız $desen yazmalısınız. Bu bağlamların ayrıntısı case yazısında, [[ ]] yazısında ve string işlemleri yazısında bulunuyor.

Glob, regex ve süslü parantez farkı

Glob, regex ve süslü parantez farkı: herhangi bir metin, boş dahil, tek bir karakter, sıradan nokta (dosya adı başında özel), [abc]

Joker karakterler düzenli ifadeler (regex) değildir; benzer görünen karakterler farklı anlam taşır. Globda * "herhangi bir metin" demektir, regex'te ise "önceki öğenin sıfır veya daha fazla tekrarı" demektir. Globda . sıradan bir karakterdir, regex'te herhangi bir karakterle eşleşir. Bash'te regex yalnızca [[ metin =~ ifade ]] içinde kullanılır; bu konu Bash regex yazısında, regex'in kendisi ise düzenli ifadeler rehberinde anlatılıyor.

Süslü parantez genişletmesi ({a,b}, {1..3}) de glob değildir. Dosya sistemine hiç bakmaz, metni kurala göre çoğaltır ve dosya adı genişletmesinden önce çalışır. echo {rapor1,yok}.txt dosya olsun ya da olmasın iki kelime üretir. Ayrıntılar brace expansion yazısında.

YazımGlob anlamıRegex anlamı
*herhangi bir metin, boş dahilönceki öğenin sıfır veya daha fazla tekrarı
?tek bir karakterönceki öğe isteğe bağlı
.sıradan nokta (dosya adı başında özel)herhangi bir karakter
[abc]a, b veya ca, b veya c
[!abc]a, b, c dışında bir karakterregexte [^abc] yazılır
**globstar açıkken klasör ağacıgeçersiz veya anlamsız

Güvenlik: tırnaksız değişkenler ve tire ile başlayan dosya adları

Dosya adı genişletmesi yalnızca sizin yazdığınız desenlere değil, tırnaksız bırakılan değişken değerlerine de uygulanır. Değişkenin içinde * varsa, tırnaksız $x klasördeki dosya adlarına dönüşür. Bu, "değişkenleri her zaman çift tırnak içine alın" kuralının en önemli nedenlerinden biridir.

bash
mkdir -p proje/src/alt
touch rapor1.txt rapor2.txt rapor10.txt Notlar.md .gizli.txt resim.PNG
touch proje/a.sh proje/src/b.sh proje/src/alt/c.sh

x="*.md"
echo $x "$x"
# çıktı: Notlar.md *.md

Bir başka risk, adı tire ile başlayan dosyalardır. * genişlediğinde -r adlı bir dosya, komut tarafından bir seçenek olarak yorumlanabilir. Bunu önlemenin iki yaygın yolu var: deseni ./* olarak yazmak, böylece her ad ./ ile başlar; ya da seçeneklerin bittiğini -- ile bildirmek. Aşağıdaki örnekte dosyaları silmiyoruz, yalnızca bir komutun alacağı argümanları gösteriyoruz.

bash
touch -- -r normal.txt
goster() { printf '[%s]\n' "$@"; }

goster *         # -r bir seçenek gibi görünür
# çıktı:
#   [-r]
#   [normal.txt]
#   [ornek.sh]
goster ./*       # her ad ./ ile başlar
# çıktı:
#   [./-r]
#   [./normal.txt]
#   [./ornek.sh]

Sık yapılan hatalar

  • Eşleşme yokken döngüye girmek: desen olduğu gibi kalır; [[ -e $f ]] || continue veya nullglob kullanın.
  • Sayısal sıralama beklemek: rapor10 metin sıralamasında rapor2 dosyasından önce gelir.
  • * ile gizli dosyaları seçtiğini sanmak: nokta ile başlayan adlar için dotglob gerekir.
  • ** yazıp globstar açmamak: kapalıyken ** tek * gibi davranır.
  • Deseni tırnaklamak: "*.txt" genişletilmez; deseni tırnak dışında bırakın.
  • Değişkeni tırnaksız kullanmak: değerdeki joker karakterler dosya adlarına dönüşebilir.
  • Glob ile regex'i karıştırmak: .* globda "nokta ile başlayan" demektir, "her şey" değil.

Sık Sorulan Sorular

ls *.txt ile ls "*.txt" farkı nedir?

İlkinde Bash deseni dosya adlarına genişletir ve ls bir dosya listesi alır. İkincisinde tırnak genişletmeyi engeller, ls adı tam olarak *.txt olan bir dosya arar ve büyük olasılıkla bulamaz.

shopt ile açtığım seçenek kalıcı mı?

Hayır, yalnızca o kabuk oturumunda veya o betikte geçerlidir. Etkileşimli kabukta her açılışta geçerli olmasını istiyorsanız komutu başlangıç dosyanıza eklersiniz; hangi dosyanın ne zaman okunduğu bashrc ve bash_profile yazısında anlatılıyor.

Joker karakterleri tamamen kapatabilir miyim?

Evet. set -f (veya set -o noglob) dosya adı genişletmesini kapatır, set +f yeniden açar. Kapalıyken echo *.txt ekrana olduğu gibi *.txt yazar.

Bir deseni dosya adı dışında, metin içinde nasıl kullanırım?

[[ $metin == desen ]] veya case ile. Bu bağlamlarda desen dosya sistemine bakmaz, yalnızca metni karşılaştırır. Daha güçlü desenler için extglob yazısına bakın.

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 · Genişletilmiş desenler (extglob) · shopt seçenekleri · Brace expansion · case esac · Bash regex eşleşmesi