Sebelum sebuah perintah jalan, shell membaca dulu baris yang kamu ketik dan mengganti pola-pola tertentu dengan teks lain. Ini namanya ekspansi. Dua yang paling sering dipakai: glob (*.log diganti jadi daftar nama file yang cocok) dan brace expansion (foto-{01..03}.jpg diganti jadi tiga kata). Bayangkan kamu bilang ke penjaga toko, "ambilkan semua yang labelnya berakhiran .log". Penjaga (shell) yang mencari dan menyerahkan barangnya satu per satu ke kasir (program seperti ls atau rm). Kasirnya gak pernah mendengar kata "semua", dia cuma menerima barangnya.
🧪 Tebakan cepat: di folder yang gak punya satu pun file
.ts, kamu menjalankanecho *.ts. Yang tercetak apa? Tebak dulu, jawabannya ada di bagian "Kalau gak ada yang cocok".
Glob: pola untuk nama file
Folder contoh kita berisi 13 file. Dua di antaranya diawali titik, jadi tersembunyi:
.env .gitignore app.js catatan.txt file1.log file2.log fileku.log
foto-01.jpg foto-02.jpg foto-03.jpg index.html readme.md style.css
| Pola | Artinya | Contoh di folder ini |
|---|---|---|
* |
nol atau lebih karakter apa saja | *.log jadi file1.log file2.log fileku.log |
? |
tepat satu karakter | file?.log jadi file1.log file2.log |
[12] |
satu karakter dari daftar | foto-0[12].jpg jadi foto-01.jpg foto-02.jpg |
[a-c] |
satu karakter dari rentang | [a-c]* jadi app.js catatan.txt |
[!f] |
satu karakter selain yang ada di daftar | [!f]* jadi semua yang gak diawali f |
$ echo file?.log
file1.log file2.log
$ echo [!f]*
app.js catatan.txt index.html readme.md style.css
file?.loggak menangkapfileku.log, karena?cuma satu karakter, sedangkankudua.- Hasilnya selalu diurutkan sesuai abjad.
- Polanya harus cocok dengan seluruh nama, dari awal sampai akhir.
*.txtgak cocok dengancatatan.txt.bak.
Shell yang menjalankan glob, bukan programnya
rm *.log gak pernah sampai ke rm dalam bentuk itu. Shell lebih dulu mengubahnya jadi rm file1.log file2.log fileku.log, baru rm jalan. Karena itu, biasakan cek dulu dengan echo: echo *.log menampilkan persis daftar yang nanti dihapus, tanpa menghapus apa pun.
Satu aturan khusus: * dan ? gak menangkap titik di awal nama. echo * mencetak 11 file dan melewatkan .env serta .gitignore. Untuk file tersembunyi, tulis titiknya: echo .* mencetak .env .gitignore (di bash 5.2 ke atas, . dan .. gak ikut).
Kalau gak ada yang cocok
Kalau glob gak menemukan satu file pun, bash membiarkan polanya apa adanya, termasuk bintangnya:
$ echo *.ts
*.ts
$ ls *.ts
ls: cannot access '*.ts': No such file or directory
Jadi echo *.ts mencetak *.ts. Itu jawaban tebakannya. ls menerima teks *.ts dan mencari file yang benar-benar bernama *.ts, makanya pesan error-nya begitu.
Kalau kamu lebih suka pola yang gak cocok langsung hilang, nyalakan shopt -s nullglob. Hati-hati: dengan nullglob, ls *.ts berubah jadi ls saja, dan malah menampilkan semua file.
Brace expansion: bikin teks, bukan cari file
Brace expansion cuma menggandakan teks. Dia gak melihat folder sama sekali, jadi hasilnya muncul walaupun filenya gak ada.
$ echo foto-{01..05}.jpg
foto-01.jpg foto-02.jpg foto-03.jpg foto-04.jpg foto-05.jpg
$ echo {app,index}.html
app.html index.html
$ echo {1..10..2}
1 3 5 7 9
| Bentuk | Hasil |
|---|---|
{a,b,c} |
a b c, dipisah koma |
{1..5} |
1 2 3 4 5 |
{01..03} |
01 02 03. Kalau salah satu ujung diawali 0, semua angka dibuat selebar yang paling panjang |
{a..e} |
a b c d e |
{1..10..2} |
1 3 5 7 9, langkah 2 |
config.json{,.bak} |
config.json config.json.bak, pilihan pertamanya kosong |
Pemakaian sehari-hari: mkdir -p proyek/{src,tests,docs} bikin tiga folder sekaligus, dan cp config.json{,.bak} bikin cadangan.
Brace jalan sebelum glob. echo *.{js,css} lebih dulu jadi *.js *.css, lalu tiap kata di-glob: hasilnya app.js style.css. Dan echo *.{js,ts} jadi app.js *.ts, karena *.ts gak cocok apa pun dan tetap apa adanya.
Glob bukan regex
Glob dan regex sama-sama pola, tapi aturannya beda. Glob khusus untuk nama file dan lebih sederhana. Regex untuk teks umum dan jauh lebih kuat.
| Glob | Regex | |
|---|---|---|
* |
karakter apa saja, berapa pun | ulangi karakter sebelumnya, nol kali atau lebih |
? |
tepat satu karakter | karakter sebelumnya boleh ada boleh gak |
. |
titik biasa | karakter apa saja |
Mengubah glob jadi regex: * jadi .*, ? jadi ., titik dan simbol lain di-escape, lalu kunci di awal dan akhir. Jadi *.txt setara regex ^.*\.txt$. Titiknya di-escape, makanya note_txt gak cocok, dan kuncinya bikin note.txt.bak juga gak cocok.
Bash juga punya pola tambahan kalau shopt -s extglob dinyalakan. Yang paling berguna, !(pola) artinya "semua kecuali yang cocok": ls !(node_modules|dist) menampilkan semua entri kecuali node_modules dan dist. Lalu ** menangkap folder di semua kedalaman, setelah shopt -s globstar: ls **/*.js.
⚠️ Jebakan yang sering ketemu
rmsebelumecho. Cek dulu hasil ekspansinya denganecho, baru jalankanrmataumvdengan pola yang sama.- Pola di dalam tanda kutip.
ls "*.log"mencari file yang benar-benar bernama*.log. Tanda kutip mematikan glob dan brace. - Spasi di dalam kurung kurawal.
echo {a, b}tercetak apa adanya, karena spasi memotongnya jadi dua kata. Tulis rapat:{a,b}. - Mengira brace mencari file.
rm foto-{01..05}.jpgmencoba lima nama, dan protes untuk yang gak ada. Pakai glob (foto-*.jpg) kalau maksudmu "yang ada saja".
TL;DR: Shell mengganti pola sebelum program jalan. Glob mencocokkan nama file yang ada: * karakter apa saja, ? tepat satu, [abc] satu dari daftar, [!abc] selain itu, dan polanya harus cocok dengan seluruh nama. * dan ? gak menangkap titik di awal nama. Kalau gak ada yang cocok, polanya tetap apa adanya (kecuali shopt -s nullglob). Brace expansion bikin teks tanpa melihat folder: {a,b}, {1..3}, {01..03} dengan nol di depan, dan jalan sebelum glob. Glob bukan regex: * glob setara .* regex, titik glob itu titik biasa. !(pola) dengan extglob artinya semua kecuali pola itu. Cek dengan echo sebelum rm.