SSD Nodes Learn 🎉 VPS mulai $5.50/bln
Panduan Matt ConnorOleh Matt Connor

Bash Globbing: Wildcard, nullglob, dotglob, dan extglob

Pahami cara Bash mengubah wildcard menjadi nama file sebelum perintah berjalan, termasuk nullglob, dotglob, globstar, dan negasi extglob.

Hal yang dilakukan bash globbing sebelum perintah dijalankan

Bash globbing adalah proses shell mengubah pola seperti *.log menjadi daftar nama file yang ada dan telah diurutkan sebelum perintah dijalankan. rm *.log tidak pernah meneruskan pola kepada rm. Bash membaca direktori, mempertahankan nama yang cocok, mengurutkannya, lalu menjalankan rm dengan setiap nama sebagai argumen terpisah. Program rm tidak dapat mengetahui bahwa sebelumnya ada pola.

Hampir semua hal yang mengejutkan tentang globbing berasal dari fakta tersebut. Pencocokan dilakukan oleh shell berdasarkan file yang ada pada saat itu, dan perintah hanya menerima daftar akhir. Istilah formalnya adalah pathname expansion. Globbing adalah istilah yang umum digunakan, dan keduanya memiliki arti yang sama.

Buat struktur direktori kecil untuk latihan

Semua contoh di bawah dijalankan terhadap file yang sama. Buat file tersebut di direktori sementara agar tidak ada file penting di sekitarnya dan proses pembersihan dapat dilakukan dengan satu perintah.

lab=$(mktemp -d)
cd "$lab"
mkdir -p logs archive/2025 archive/2026
touch app.log app.log.1 app.log.2 app.log.10 error.log debug.LOG
touch notes.txt notes.md README 'weekly report.log'
touch .env .apprc
touch logs/nginx.log logs/nginx.log.1
touch archive/2025/dec.log archive/2026/jan.log

mktemp -d membuat direktori baru dan mengembalikan path-nya, sedangkan $(...) menyimpan path tersebut ke dalam variabel lab. Jika sintaks tersebut masih baru bagi Anda, cara substitusi perintah menyimpan output perintah menjelaskannya secara lengkap.

Periksa hasil yang Anda buat dan cek shell Anda, karena salah satu catatan di bagian akhir bergantung pada versinya:

ls -a
bash --version

Semua bagian di sini berfungsi pada bash 4.0 dan yang lebih baru. Catatan globskipdots di dekat bagian akhir berlaku pada bash 5.2 dan yang lebih baru, yang digunakan oleh rilis Ubuntu dan Debian saat ini per Agustus 2026.

Perintah pratinjau yang digunakan di seluruh tutorial ini adalah printf '%s\n' PATTERN. Perintah tersebut mencetak satu hasil yang cocok per baris dan tidak mengubah apa pun di disk, sehingga aman dijalankan pada pola apa pun yang belum Anda yakini.

Tiga wildcard: star, tanda tanya, dan tanda kurung siku

  • * cocok dengan rangkaian karakter apa pun, termasuk rangkaian kosong.
  • ? cocok dengan tepat satu karakter.
  • [...] cocok dengan tepat satu karakter yang diambil dari set di dalam tanda kurung siku.
printf '%s\n' *.log
printf '%s\n' app.log.?
printf '%s\n' app.log.[0-9]
printf '%s\n' notes.*
printf '%s\n' *

Bandingkan baris kedua dengan daftar file yang Anda buat. ? mewakili satu karakter saja, sehingga sufiks dua digit tidak cocok dengan pola tersebut. Perluas menjadi app.log.[0-9]*, lalu jalankan lagi untuk melihat perbedaannya.

Dua aturan berlaku untuk ketiga wildcard. Wildcard tidak pernah cocok dengan /, sehingga pola tetap berada dalam satu tingkat direktori. Selain itu, . di awal harus diketik secara literal, sehingga pola yang diawali * melewati semua nama tersembunyi. Aturan kedua inilah yang membuat rm * tidak menyertakan dotfile Anda.

Hasil glob diurutkan menggunakan urutan kolasi locale saat ini, yaitu pengurutan teks karakter demi karakter. Karena itu, sufiks 10 diurutkan sebelum sufiks 2, karena perbandingan tidak pernah membaca keduanya sebagai angka. Jalankan printf '%s\n' app.log.*, lalu ls -v app.log.* dan bandingkan hasilnya: ls -v mengurutkan angka yang berada di dalam teks berdasarkan nilainya, sedangkan sort -V melakukan hal yang sama dalam pipeline.

Kelas karakter dan rentang

Di dalam tanda kurung siku, Anda dapat menulis daftar, rentang, atau negasi.

  • [ch] mencocokkan satu karakter, yaitu c atau h.
  • [a-f] mencocokkan satu karakter dalam rentang a hingga f.
  • [!0-9] mencocokkan satu karakter yang bukan digit. [^0-9] memiliki arti yang sama.
printf '%s\n' [dn]*
printf '%s\n' *.[A-Z]*
printf '%s\n' [A-Z]*

Rentang adalah bagian yang perilakunya dapat berbeda antar-mesin. Rentang ditentukan berdasarkan urutan kolasi locale saat ini, bukan ASCII biasa. Karena itu, pada beberapa sistem [a-z] juga mencocokkan huruf besar. Bash memiliki opsi globasciiranges yang memaksa urutan ASCII biasa. Build terbaru mengaktifkannya secara default, jadi periksa konfigurasi Anda dengan shopt globasciiranges, bukan dengan berasumsi. Skrip yang harus mencocokkan pola dengan cara yang sama di semua tempat dapat menetapkan sendiri urutannya dengan menulis LC_ALL=C pada baris tersendiri, di dekat bagian atas skrip. Dengan demikian, setiap rentang kembali menggunakan urutan byte biasa.

Penetapan tersebut harus menjadi perintah tersendiri. Jika ditulis sebagai awalan, LC_ALL=C ls [a-z]* menetapkan locale hanya untuk ls. Shell sudah memperluas pola menggunakan locale-nya sendiri sebelum ls dimulai. Ini merupakan penerapan kembali aturan dari bagian pertama.

Ekspansi brace bukan globbing

Brace terlihat seperti wildcard, tetapi perilakunya sama sekali berbeda. Ekspansi brace berlangsung jauh lebih awal daripada globbing dan tidak pernah memeriksa disk.

echo file{1,2,3}.txt
echo {01..10}
echo {a..e}
echo {0..20..5}

Tidak ada satu pun file tersebut yang ada, tetapi ekspansi tetap berlangsung. Inilah perbedaan utamanya: glob menanyakan sesuatu kepada filesystem, sedangkan brace hanya menghasilkan teks. Karena itu, brace lebih tepat digunakan untuk membuat sesuatu daripada mencarinya.

mkdir -p site/{css,js,img}
cp app.log{,.bak}
touch report-{2025,2026}-{01,02}.csv

cp app.log{,.bak} diekspansi menjadi dua kata, app.log dan app.log.bak, karena item kosong sebelum koma menghasilkan kembali teks asli. {01..10} mempertahankan padding nolnya karena angka 0 di awal salah satu endpoint membuat bash mengisi semua angka yang dihasilkan hingga memiliki lebar yang sama.

Urutan ekspansi juga menjelaskan kegagalan yang sering terjadi. Bash mengekspansi brace terlebih dahulu, kemudian variabel, dan melakukan ekspansi pathname terakhir.

n=5
echo {1..$n}

Ekspansi brace sudah selesai ketika $n menjadi angka, sehingga tidak ada range yang terbentuk. Gunakan seq 1 "$n" atau loop bergaya C yang ditulis sebagai for ((i=1; i<=n; i++)).

Yang terjadi saat suatu pola tidak cocok dengan apa pun

Ini adalah mode kegagalan pertama, dan semua orang pernah mengalaminya. Secara default, bash membiarkan pola yang tidak cocok apa adanya. Kata tersebut diteruskan ke perintah persis seperti yang Anda ketik, sehingga perintah menerima karakter *, ., t, m, dan p lalu mencoba memperlakukannya sebagai nama file.

printf '%s\n' *.tmp

Hal itu tidak berbahaya dengan printf. Namun, hal itu berbahaya dengan perintah yang membuat sesuatu. touch *.tmp dalam direktori tanpa file .tmp akan membuat file yang namanya benar-benar *.tmp. Untuk menghapusnya, Anda perlu menggunakan tanda kutip agar shell tidak memperluasnya lagi: rm -- '*.tmp'.

Dua opsi shell mengubah aturan ini. Uji masing-masing opsi dengan pola yang tidak cocok dengan apa pun, lalu nonaktifkan kembali.

shopt -s nullglob
printf '%s\n' *.tmp
shopt -u nullglob

shopt -s failglob
printf '%s\n' *.tmp
shopt -u failglob

nullglob membuat pola yang tidak cocok diperluas menjadi tidak ada sama sekali, sehingga perintah dijalankan dengan jumlah argumen yang lebih sedikit daripada yang Anda tulis. Inilah yang Anda inginkan saat menggunakan loop. Jika nullglob nonaktif, for f in *.tmp menjalankan isi loop satu kali dengan f yang berisi pola literal. Ini merupakan bug pada hampir setiap skrip yang melakukannya. Jika nullglob aktif, isi loop tidak dijalankan sama sekali.

nullglob memiliki bahayanya sendiri karena perintah yang seluruh argumennya dihapus tetap dijalankan. ls *.nope berubah menjadi ls tanpa argumen, yang menampilkan seluruh isi direktori. grep -l needle *.nope berubah menjadi grep -l needle, yang tidak memiliki file untuk dibaca, sehingga menunggu pada standard input dan terlihat seperti macet. Aktifkan nullglob di sekitar loop yang membutuhkannya, lalu nonaktifkan kembali setelahnya.

failglob menggunakan pendekatan sebaliknya: pola yang tidak cocok dianggap sebagai error, bash melaporkannya, dan perintah sama sekali tidak dijalankan. Ini merupakan pengaturan yang baik untuk shell interaktif karena mencegah pola yang salah ketik diteruskan ke rm sebagai nama literal.

Berkas tersembunyi: dotglob

shopt -s dotglob
printf '%s\n' *
shopt -u dotglob

Jalankan blok tersebut, lalu bandingkan hasil pencantumannya dengan printf '%s\n' * biasa dari bagian sebelumnya. dotglob menghapus aturan titik di awal nama, sehingga * juga mencocokkan nama tersembunyi. Entri . dan .. tetap dikecualikan saat dotglob aktif.

Tanpa dotglob, .* biasanya digunakan untuk mengakses berkas tersembunyi, tetapi cara ini dahulu berbahaya. Pada bash sebelum versi 5.2, .* juga mencocokkan . dan .., sehingga perintah rekursif seperti chmod -R 755 .* langsung masuk ke direktori induk. Bash 5.2 menambahkan opsi globskipdots, yang diaktifkan secara default, sehingga . dan .. tidak disertakan dalam ekspansi apa pun. Jalankan shopt globskipdots sebelum mengandalkannya, karena sistem lama tidak memiliki opsi tersebut.

globstar: mencocokkan hingga seluruh hierarki direktori

shopt -s globstar
printf '%s\n' **/*.log
printf '%s\n' **/
shopt -u globstar

Jika globstar diaktifkan, ** yang membentuk satu komponen path akan mencocokkan file dan direktori pada kedalaman apa pun, termasuk nol tingkat ke bawah. Jadi, **/*.log mencakup direktori saat ini, logs/, dan semua yang berada di bawah archive/. **/ dengan garis miring di akhir hanya mencocokkan direktori, sehingga menjadi cara cepat untuk melihat struktur hierarki direktori.

Jika globstar tidak diaktifkan, ** berperilaku seperti * biasa dan tetap berada dalam satu direktori. Jalankan pola yang sama ketika opsi tersebut dinonaktifkan, lalu bandingkan hasilnya. Pola rekursif yang disalin dari tempat lain tetapi mengembalikan hasil jauh lebih sedikit dari yang diharapkan biasanya mengalami masalah ini. globstar secara default dinonaktifkan di setiap bash.

Ada satu batasan yang perlu diketahui: ** tidak mengikuti symbolic link menuju direktori. Hierarki yang disusun menggunakan symbolic link memerlukan find -L.

nocaseglob, untuk nama yang perbedaan huruf besarnya tidak konsisten

shopt -s nocaseglob
printf '%s\n' *.log
shopt -u nocaseglob

nocaseglob membuat pencocokan nama file mengabaikan huruf besar-kecil. Dengan demikian, akhiran .LOG dalam huruf besar cocok dengan pola .log dalam huruf kecil. Bandingkan daftar ini dengan daftar dari bagian pertama. Segera nonaktifkan kembali opsi tersebut. Jika dibiarkan aktif, opsi ini mengubah pencocokan untuk setiap perintah berikutnya dalam shell tersebut. Kondisi ini akan membingungkan saat harus melakukan debug satu jam kemudian.

nocaseglob hanya berlaku untuk ekspansi nama file. Opsi terkait untuk pencocokan pola di dalam pernyataan case atau pengujian kurung ganda adalah nocasematch. Keduanya diatur secara terpisah.

extglob: pola yang dapat menyatakan "bukan ini"

Pola diperluas dinonaktifkan secara default. Aktifkan pada baris tersendiri:

shopt -s extglob

Ada lima bentuk. Masing-masing menerima daftar pola yang dipisahkan oleh |:

  • ?(list) cocok dengan nol atau satu kemunculan.
  • *(list) cocok dengan nol atau lebih kemunculan.
  • +(list) cocok dengan satu atau lebih kemunculan.
  • @(list) cocok tepat dengan salah satu alternatif.
  • !(list) cocok dengan apa pun yang tidak cocok dengan alternatif mana pun.

Pola diperluas harus dibaca oleh shell yang opsinya sudah aktif. Karena itu, batch di bawah ini ditempatkan dalam script kecil yang dijalankan dengan bash -O extglob. Flag -O menetapkan opsi shopt sebelum shell baru membaca satu baris file. Dengan demikian, setiap pola dapat diparse dengan benar. Paragraf terakhir di bagian ini menjelaskan alasan urutan tersebut penting.

cat > patterns.sh <<'EOF'
printf '%s\n' @(app|error).log
printf '%s\n' app.log.+([0-9])
printf '%s\n' *.@(md|txt)
printf '%s\n' !(*.log)
printf '%s\n' !(*.log|*.md|README)
EOF
bash -O extglob patterns.sh

+([0-9]) berarti satu atau lebih digit. Jadi, pola ini mencakup suffix bernomor dengan panjang berapa pun. Ini memperbaiki batas ? yang dijelaskan sebelumnya. Jalankan kedua pola secara berurutan untuk melihat perbedaannya.

Bentuk negasi menjawab kebutuhan untuk memilih "semuanya kecuali". Bentuk ini juga menjadi alasan utama untuk mengaktifkan extglob. Ada dua hal yang sering mengejutkan pengguna.

Bentuk ini mencocokkan direktori dan file. Karena itu, ls !(*.log) menampilkan isi setiap direktori yang cocok, bukan nama direktorinya. Gunakan ls -d atau preview printf untuk melihat nama direktori itu sendiri.

Bentuk ini juga mengikuti aturan dot seperti glob lainnya. Jadi, !(*.log) mencakup setiap nama yang terlihat dan tidak diakhiri dengan .log. Untuk mencakup nama tersembunyi, dotglob juga harus ditetapkan.

! dalam !(...) adalah negasi glob. Ini tidak berkaitan dengan ekspansi history dan karakter bang, yang merupakan fitur terpisah dan dijalankan pada tahap berbeda.

Ada satu jebakan lagi, dan ini terjadi saat parsing. Bash memparse perintah lengkap sebelum menjalankan bagiannya. Karena itu, mengaktifkan extglob dan menggunakan pola diperluas pada baris yang sama akan gagal: pola tersebut diparse saat opsi masih nonaktif. Letakkan shopt -s extglob pada baris tersendiri, sebelum pola, dan dekat bagian atas script. Isi fungsi diparse saat fungsi didefinisikan. Jadi, opsi harus aktif sebelum definisi, bukan sebelum pemanggilan. Memulai shell dengan bash -O extglob, seperti pada batch di atas, memastikan urutan tersebut sebelum parsing apa pun dimulai.

Shell memperluas pola, bukan perintah

Ini adalah mode kegagalan kedua dan menjelaskan seluruh kelompok bug.

grep -l needle *.log
find . -name '*.log'
find . -name *.log

Baris pertama membiarkan bash melakukan pencocokan, lalu grep menerima daftar nama file. Baris kedua mengapit pola dengan tanda kutip, sehingga find menerima lima karakter *.log dan melakukan pencocokan sendiri pada setiap kedalaman di bawah titik awal. Baris ketiga adalah bug: bash lebih dahulu memperluas pola berdasarkan direktori saat ini, sehingga find diperintahkan untuk mencari satu nama tertentu. Dalam direktori dengan satu hasil yang cocok, perintah tersebut secara diam-diam mencari hal yang salah. Dengan dua hasil atau lebih, find melaporkan kesalahan penggunaan karena nama-nama tambahan masuk ke posisi yang seharusnya berisi ekspresi.

Aturannya singkat. Jika pola ditujukan untuk perintah, apit dengan tanda kutip. Jika pola ditujukan untuk shell, biarkan tanpa tanda kutip. Pembagian yang sama berlaku untuk pola --exclude dalam tar dan rsync, serta filter --include dalam grep.

Glob juga bukan ekspresi reguler, meskipun menggunakan beberapa karakter yang sama. Dalam glob, * berarti rangkaian karakter apa pun. Dalam ekspresi reguler, * berarti nol atau lebih kemunculan dari elemen sebelumnya, sehingga grep '*.log' meminta sesuatu yang sangat berbeda dari tampilannya.

Variabel mengikuti urutan operasi yang sama. Globbing berjalan setelah perluasan variabel, sehingga variabel tanpa tanda kutip yang berisi pola akan diperluas berdasarkan direktori.

pat='*.log'
printf '%s\n' $pat
printf '%s\n' "$pat"

Tanpa tanda kutip, bash melakukan glob pada hasil variabel. Dengan tanda kutip, pola tetap literal. Ada konsekuensi terkait yang perlu diandalkan: kata-kata yang dihasilkan oleh globbing tidak pernah dipecah lagi berdasarkan spasi, sehingga for f in *.log menangani nama file yang berisi spasi sebagai satu nama, sedangkan for f in $(ls *.log) memecah nama tersebut menjadi dua kata. Lakukan loop langsung pada glob.

Pratinjau pola destruktif sebelum menjalankannya

Jangan pernah menjadikan rm sebagai perintah pertama untuk mencoba pola baru. Jalankan pola tersebut dengan perintah yang aman, baca daftarnya, lalu ubah hanya perintah di awal baris.

target='!(*.log)'
printf '%s\n' $target
ls -ld -- $target
rm -- $target

Menulis pola ke dalam target satu kali mencegah ketiga baris tersebut tidak sinkron. $target sengaja dibiarkan tanpa tanda kutip karena itulah yang memungkinkan bash melakukan ekspansi. printf '%s\n' mencetak satu nama per baris dan tidak mengubah apa pun. ls -ld menampilkan setiap entri, bukan isi direktori, dan kolom mode menandai entri yang merupakan direktori. Kolom inilah yang harus diperiksa sebelum penghapusan rekursif. Membaca bit permission drwxr-xr-x membahas kolom tersebut.

Panggil kembali baris sebelumnya dengan tombol panah atas, lalu edit hanya perintahnya agar polanya tetap sama. Mengetik ulang pola berdasarkan ingatan adalah saat kesalahan dapat terjadi.

Letakkan -- sebelum pola. Perintah ini menandai akhir opsi, sehingga nama file yang diawali - diperlakukan sebagai nama, bukan flag.

Jika Anda ingin menonaktifkan globbing selama bagian tertentu dalam skrip, set -f menonaktifkannya dan set +f mengaktifkannya kembali.

Kebiasaan ini penting karena rm tidak memiliki fitur undo. Memulihkan file yang dihapus dengan rm -rf membutuhkan waktu dan biasanya tidak lengkap, sehingga pratinjau jauh lebih murah daripada akibatnya.

Bersihkan direktori latihan setelah selesai. Cetak path tersebut dan baca terlebih dahulu, karena rm -rf yang mengarah ke variabel yang salah adalah kecelakaan yang dibahas dalam bagian ini.

cd ~
echo "$lab"
rm -rf -- "$lab"

FAQ

Mengapa find . -name *.log menemukan file yang salah?

Karena bash memperluas *.log sebelum find berjalan. Shell mencocokkan pola dengan direktori saat ini, lalu meneruskan nama file yang dihasilkan ke find. Setelah itu, find mencari satu nama tersebut pada setiap tingkat direktori. Jika dua file atau lebih cocok, find menerima argumen tambahan pada posisi yang seharusnya berisi ekspresi dan melaporkan kesalahan penggunaan. Beri tanda kutip pada pola sebagai find . -name '*.log' agar find melakukan pencocokan sendiri, dengan aturan dan rekursinya sendiri.

Apa artinya jika pola saya muncul sebagai teks literal?

Artinya tidak ada yang cocok. Secara default, bash membiarkan pola yang tidak cocok tetap apa adanya dan meneruskan karakter mentah tersebut ke perintah. Perintah kemudian memperlakukannya sebagai nama file. Atur shopt -s failglob untuk mengubah pola yang tidak cocok menjadi error yang menghentikan perintah, atau gunakan shopt -s nullglob agar pola tersebut diperluas menjadi kosong. Dengan nullglob, pastikan perintah tetap masuk akal saat tidak menerima argumen sama sekali. Jika tidak, ls *.nope menjadi ls tanpa argumen dan menampilkan seluruh direktori.

Bagaimana cara mencocokkan semua file kecuali satu pola di bash?

Aktifkan pola extended dengan shopt -s extglob pada baris tersendiri, lalu gunakan bentuk negasi. !(*.log) mencocokkan nama yang tidak diakhiri .log, sedangkan !(*.log|*.md) mengecualikan keduanya. File tersembunyi tetap tidak disertakan kecuali dotglob juga diatur. Direktori juga disertakan, jadi pratinjau pola tersebut dengan printf '%s\n' !(*.log) sebelum meneruskannya ke rm.

Mengapa * melewati file tersembunyi?

Titik di awal nama harus dicocokkan secara literal, sehingga * tidak pernah mencocokkan nama yang diawali titik. Gunakan .* untuk menjangkau nama tersembunyi, atau atur shopt -s dotglob agar nama tersebut disertakan dalam setiap pola biasa. Entri . dan .. selalu dikecualikan saat dotglob aktif. Pada bash 5.2 dan versi setelahnya, entri tersebut juga tetap dikecualikan dari .* melalui opsi globskipdots, yang aktif secara default.

Apakah ** memerlukan globstar agar dapat berfungsi?

Ya. Tanpa shopt -s globstar, bash memperlakukan ** sebagai * biasa. Pola tersebut hanya mencocokkan isi dalam satu direktori, lalu berhenti. Jika globstar aktif, ** yang membentuk seluruh komponen path dapat mencocokkan pada tingkat kedalaman apa pun. Dengan demikian, **/*.log menjangkau direktori saat ini dan semua subdirektori di bawahnya, sedangkan **/ jika digunakan sendiri hanya mencocokkan direktori. Perhatikan bahwa ** tidak mengikuti symbolic link ke direktori. Gunakan find -L untuk tree yang dibangun dari symbolic link.

#bash#globbing#shell#linux#scripting