Cara Menggunakan Bash Globbing dan Wildcard
Ketahui cara Bash menukar corak wildcard kepada senarai nama fail sebelum arahan dijalankan. Fahami fungsi nullglob, dotglob, globstar serta teknik penafian extglob.
Apakah yang dilakukan oleh globbing bash sebelum arahan anda dijalankan
Globbing bash ialah proses shell menukarkan corak seperti *.log kepada senarai nama fail sebenar yang disusun, sebelum arahan dimulakan. rm *.log tidak pernah menyerahkan rm sebagai satu corak. Bash membaca direktori, menyimpan nama yang sepadan, menyusunnya, dan kemudian menjalankan rm dengan setiap nama sebagai argumen yang berasingan. Program rm tidak dapat mengetahui bahawa sesuatu corak pernah terlibat.
Hampir semua perkara yang mengejutkan tentang globbing berpunca daripada fakta tersebut. Pemadanan dilakukan oleh shell, berdasarkan fail yang wujud pada waktu itu, dan arahan hanya melihat senarai yang telah siap. Nama rasmi bagi proses ini ialah pengembangan laluan fail (pathname expansion). Globbing ialah istilah harian, dan kedua-duanya membawa maksud yang sama.
Bina pepohon kecil untuk latihan
Setiap contoh di bawah dijalankan terhadap fail yang sama. Cipta fail tersebut dalam direktori sementara supaya tiada fail penting berdekatan dan pembersihan boleh dilakukan dengan satu arahan.
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.logmktemp -d mencipta direktori baharu dan mengembalikan laluannya, manakala $(...) menangkap laluan tersebut ke dalam pemboleh ubah lab. Jika sintaks ini baharu bagi anda, bagaimana penggantian arahan menangkap output arahan menerangkannya dengan lengkap.
Lihat apa yang anda bina, dan periksa shell anda, kerana satu nota di bahagian akhir bergantung pada versi:
ls -a
bash --versionSegala-galanya di sini berfungsi pada bash 4.0 dan ke atas. Nota globskipdots berhampiran bahagian akhir terpakai untuk bash 5.2 dan ke atas, iaitu versi yang disertakan dalam keluaran Ubuntu dan Debian semasa setakat Ogos 2026.
Arahan pratonton yang digunakan di seluruh bahagian ini ialah printf '%s\n' PATTERN. Ia mencetak satu padanan bagi setiap baris dan tidak mengubah apa-apa pada cakera, jadi ia selamat untuk dijalankan terhadap sebarang corak yang anda tidak pasti.
Tiga wildcard: bintang, tanda soal dan kurungan
*memadankan sebarang rentetan aksara, termasuk yang kosong.?memadankan tepat satu aksara.[...]memadankan tepat satu aksara yang diambil daripada set di dalam kurungan.
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 senarai fail yang anda cipta. ? mewakili satu aksara dan tidak lebih daripada itu, jadi akhiran dua digit tidak termasuk dalam corak tersebut. Luaskan kepada app.log.[0-9]* dan jalankan semula untuk melihat perbezaannya.
Dua peraturan terpakai untuk ketiga-tiga wildcard ini. Tiada wildcard yang memadankan /, jadi sesuatu corak kekal dalam satu tahap direktori. Selain itu, . di permulaan perlu ditaip secara literal, jadi corak yang bermula dengan * akan melangkau setiap nama tersembunyi. Peraturan kedua itulah sebabnya rm * tidak mengusik dotfiles anda.
Hasil glob tiba dalam keadaan tersusun, menggunakan tertib kolasi locale semasa, iaitu susunan teks aksara demi aksara. Oleh itu, akhiran 10 disusun sebelum akhiran 2, kerana perbandingan tersebut tidak pernah membaca mana-mana daripadanya sebagai nombor. Jalankan printf '%s\n' app.log.* dan kemudian ls -v app.log.* serta bandingkan kedua-duanya: ls -v menyusun nombor terbenam mengikut nilai, dan sort -V melakukan tugas yang sama dalam pipeline.
Kelas aksara dan julat
Di dalam kurungan, anda boleh menulis senarai, julat, atau penafian.
[ch]memadankan satu aksara, sama adacatauh.[a-f]memadankan satu aksara dalam julat a hingga f.[!0-9]memadankan satu aksara yang bukan digit.[^0-9]membawa maksud yang sama.
printf '%s\n' [dn]*
printf '%s\n' *.[A-Z]*
printf '%s\n' [A-Z]*Julat merupakan bahagian yang sering menyebabkan perbezaan antara mesin. Julat diselesaikan mengikut tertib kolasi (collation order) lokal semasa dan bukannya ASCII biasa, jadi pada sesetengah sistem [a-z] juga memadankan huruf besar. Bash mempunyai pilihan globasciiranges yang memaksa tertib ASCII biasa, dan binaan terkini mendayakannya secara lalai, jadi semak tetapan anda dengan shopt globasciiranges dan jangan membuat andaian. Skrip yang perlu memadankan cara yang sama di mana-mana sahaja boleh menetapkan tertib tersebut dengan meletakkan LC_ALL=C pada barisnya sendiri, berhampiran bahagian atas, yang mengembalikan setiap julat kepada tertib bait biasa.
Tugasan tersebut mestilah merupakan satu arahan yang berdiri sendiri. Jika ditulis sebagai awalan, LC_ALL=C ls [a-z]* menetapkan lokal untuk ls sahaja dan tiada yang lain, manakala shell telah pun mengembangkan corak tersebut dalam lokalnya sendiri sebelum ls bermula, yang merupakan peraturan daripada bahagian pertama yang muncul semula.
Brace expansion bukan globbing
Brace expansion kelihatan seperti wildcard tetapi fungsinya sama sekali berbeza. Brace expansion dijalankan jauh lebih awal daripada globbing, dan ia tidak pernah menyemak cakera.
echo file{1,2,3}.txt
echo {01..10}
echo {a..e}
echo {0..20..5}Tiada satu pun fail tersebut wujud, namun pengembangan tetap berlaku. Itulah perbezaan keseluruhannya: glob menanyakan soalan kepada sistem fail, manakala brace hanya menjana teks. Inilah sebabnya brace ialah alat yang tepat untuk mencipta sesuatu dan bukannya mencari sesuatu.
mkdir -p site/{css,js,img}
cp app.log{,.bak}
touch report-{2025,2026}-{01,02}.csvcp app.log{,.bak} berkembang menjadi dua perkataan, app.log dan app.log.bak, kerana item kosong sebelum koma menghasilkan semula teks asal. {01..10} mengekalkan pelapik sifar (zero padding) kerana sifar di hadapan pada mana-mana titik akhir menyebabkan bash melapik setiap nombor yang dijana kepada lebar yang sama.
Urutan pengembangan juga menjelaskan kegagalan yang sering dihadapi pengguna. Bash mengembangkan brace terlebih dahulu, kemudian pemboleh ubah, dan pengembangan nama laluan (pathname expansion) dilakukan terakhir.
n=5
echo {1..$n}Brace expansion telah pun selesai sebelum $n menjadi nombor, jadi tiada julat yang dibina. Gunakan seq 1 "$n", atau gelung gaya C yang ditulis sebagai for ((i=1; i<=n; i++)).
Apa yang berlaku apabila corak tidak sepadan dengan apa-apa
Ini adalah mod kegagalan pertama, dan semua orang pernah mengalaminya sekurang-kurangnya sekali. Secara lalai, bash membiarkan corak yang tidak sepadan tidak berubah. Perkataan tersebut sampai kepada arahan tepat seperti yang anda taip, jadi arahan tersebut menerima aksara *, ., t, m dan p lalu cuba melayannya sebagai nama fail.
printf '%s\n' *.tmpPerkara ini tidak berbahaya dengan printf. Ia berbahaya dengan arahan yang mencipta sesuatu. touch *.tmp dalam direktori yang tiada fail .tmp akan mencipta fail yang namanya secara literal *.tmp, dan memadamnya kemudian memerlukan penggunaan tanda petik supaya shell tidak mengembangkannya semula: rm -- '*.tmp'.
Dua pilihan shell mengubah peraturan ini. Cuba setiap satu terhadap corak yang tidak sepadan, kemudian nyahset pilihan tersebut.
shopt -s nullglob
printf '%s\n' *.tmp
shopt -u nullglob
shopt -s failglob
printf '%s\n' *.tmp
shopt -u failglobnullglob menyebabkan corak yang tidak sepadan berkembang menjadi tiada apa-apa, jadi arahan dijalankan dengan argumen yang lebih sedikit daripada yang anda tulis. Itulah yang anda mahukan di sekitar gelung (loop). Dengan nullglob dimatikan, for f in *.tmp menjalankan badannya sekali dengan f memegang corak literal tersebut, yang merupakan pepijat dalam hampir setiap skrip yang melakukannya. Dengan nullglob dihidupkan, badan tersebut dijalankan sifar kali.
nullglob membawa bahayanya sendiri, kerana arahan yang dilucutkan daripada semua argumennya masih akan berjalan. ls *.nope menjadi ls kosong, yang menyenaraikan keseluruhan direktori. grep -l needle *.nope menjadi grep -l needle, yang tiada fail untuk dibaca, jadi ia menunggu pada input standard dan kelihatan seperti telah terhenti (frozen). Hidupkan nullglob di sekitar gelung yang memerlukannya, dan matikan semula selepas itu.
failglob mengambil laluan yang berbeza: corak yang tidak sepadan adalah ralat, bash melaporkannya, dan arahan tersebut tidak akan dijalankan langsung. Ini adalah tetapan yang baik untuk shell interaktif, kerana ia menghalang corak yang tersalah taip daripada diserahkan kepada rm sebagai nama literal.
Fail tersembunyi: dotglob
shopt -s dotglob
printf '%s\n' *
shopt -u dotglobJalankan blok tersebut dan bandingkan senarai yang terhasil dengan printf '%s\n' * biasa daripada langkah sebelum ini. dotglob membuang peraturan titik di hadapan, jadi * turut memadankan nama fail tersembunyi. Entri . dan .. sentiasa dikecualikan di bawah dotglob, tanpa pengecualian.
Tanpa dotglob, .* ialah cara biasa untuk mencapai fail tersembunyi, dan ia pernah dianggap berbahaya. Pada bash versi sebelum 5.2, .* turut memadankan . dan .., jadi arahan rekursif seperti chmod -R 755 .* akan terus masuk ke direktori induk. Bash 5.2 menambah pilihan globskipdots, yang diaktifkan secara lalai, untuk memastikan . dan .. tidak disertakan dalam setiap pengembangan. Jalankan shopt globskipdots sebelum anda bergantung padanya, kerana sistem yang lebih lama tidak mempunyai ciri ini.
globstar: memadankan keseluruhan struktur direktori
shopt -s globstar
printf '%s\n' **/*.log
printf '%s\n' **/
shopt -u globstarApabila globstar ditetapkan, ** yang membentuk komponen laluan lengkap akan memadankan fail dan direktori pada sebarang kedalaman, termasuk sifar tahap ke bawah. Oleh itu, **/*.log merangkumi direktori semasa serta logs/ dan segala kandungan di bawah archive/. **/ dengan garis miring (trailing slash) hanya memadankan direktori, menjadikannya cara pantas untuk melihat bentuk sesuatu struktur pokok direktori.
Tanpa globstar ditetapkan, ** berkelakuan seperti * biasa dan kekal dalam satu direktori sahaja. Jalankan corak yang sama dengan pilihan tersebut dimatikan dan buat perbandingan: corak rekursif yang disalin dari tempat lain yang memberikan hasil jauh lebih sedikit daripada jangkaan biasanya berpunca daripada perkara ini. globstar dimatikan secara lalai dalam setiap bash.
Satu had yang perlu diketahui: ** tidak mengikuti pautan simbolik (symbolic links) ke direktori. Struktur pokok yang digabungkan menggunakan symlink memerlukan find -L sebagai gantinya.
nocaseglob, untuk nama yang berbeza dari segi huruf besar dan kecil
shopt -s nocaseglob
printf '%s\n' *.log
shopt -u nocaseglobnocaseglob menjadikan pemadanan nama fail mengabaikan perbezaan huruf besar dan kecil, jadi akhiran .LOG huruf besar akan memadankan corak .log huruf kecil. Bandingkan senarai ini dengan senarai daripada bahagian pertama. Matikan pilihan ini semula dengan segera, kerana jika dibiarkan aktif, ia akan mengubah pemadanan bagi setiap arahan yang menyusul dalam shell tersebut, dan ini merupakan perkara yang mengelirukan untuk dinyahpepijat sejam kemudian.
nocaseglob hanya terpakai pada pengembangan nama fail. Pilihan yang berkaitan untuk pemadanan corak di dalam pernyataan case atau ujian kurungan berkembar ialah nocasematch, dan kedua-duanya ditetapkan secara berasingan.
extglob: corak yang boleh menyatakan "bukan ini"
Corak lanjutan dinyahaktifkan secara lalai. Aktifkannya pada baris yang berasingan:
shopt -s extglobTerdapat lima bentuk, setiap satunya mengambil senarai corak yang dipisahkan oleh |:
?(list)memadankan sifar atau satu kejadian.*(list)memadankan sifar atau lebih.+(list)memadankan satu atau lebih.@(list)memadankan tepat satu daripada alternatif.!(list)memadankan apa sahaja yang tidak sepadan dengan mana-mana alternatif.
Corak lanjutan perlu dibaca oleh shell yang sudah mengaktifkan pilihan tersebut, jadi kelompok di bawah diletakkan dalam skrip kecil yang dimulakan dengan bash -O extglob. Flag -O menetapkan pilihan shopt sebelum shell baharu membaca satu baris fail tersebut, yang bermaksud setiap corak di dalamnya dihurai dengan betul. Perenggan terakhir bahagian ini adalah sebab mengapa susunan adalah 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]) bermaksud satu atau lebih digit, jadi ia meliputi akhiran bernombor dengan sebarang panjang. Itu adalah penyelesaian bagi had ? daripada bahagian sebelum ini, dan adalah berbaloi untuk menjalankan kedua-dua corak secara berturutan untuk melihatnya.
Bentuk penafian adalah jawapan kepada "semua kecuali", dan ia merupakan sebab utama untuk mengaktifkan extglob. Dua perkara mengenainya mengejutkan pengguna.
Ia memadankan direktori serta fail, jadi ls !(*.log) menyenaraikan kandungan mana-mana direktori dalam hasil tersebut dan bukannya nama direktori itu sendiri. Gunakan ls -d, atau pratonton printf, untuk melihat nama-nama itu sendiri.
Ia juga mematuhi peraturan titik seperti glob yang lain, jadi !(*.log) meliputi setiap nama yang kelihatan yang tidak berakhir dengan .log. Nama tersembunyi memerlukan dotglob ditetapkan juga.
! dalam !(...) ialah penafian glob. Ia tiada kaitan dengan pengembangan sejarah dan aksara bang, yang merupakan ciri berasingan yang berjalan pada peringkat yang berbeza.
Satu lagi perangkap, dan ini berlaku pada masa penghuraian. Bash menghurai arahan lengkap sebelum ia menjalankan mana-mana bahagiannya, jadi mengaktifkan extglob dan menggunakan corak lanjutan pada baris yang sama akan gagal: corak tersebut dihurai semasa pilihan masih dimatikan. Pastikan shopt -s extglob berada pada barisnya sendiri, sebelum corak, dan berhampiran bahagian atas skrip. Badan fungsi dihurai apabila fungsi tersebut ditakrifkan, jadi pilihan tersebut perlu diaktifkan sebelum takrifan, bukan sebelum panggilan. Memulakan shell dengan bash -O extglob, seperti yang dilakukan oleh kelompok di atas, menyelesaikan isu susunan sebelum sebarang penghuraian bermula.
Shell mengembangkan corak, bukan arahan
Ini ialah mod kegagalan kedua, dan ia menjelaskan satu keluarga pepijat yang menyeluruh.
grep -l needle *.log
find . -name '*.log'
find . -name *.logBaris pertama membiarkan bash melakukan pemadanan, dan grep menerima senarai nama fail. Baris kedua meletakkan tanda petikan pada corak tersebut, jadi find menerima lima aksara *.log dan melakukan pemadanannya sendiri, pada setiap kedalaman di bawah titik permulaan. Baris ketiga ialah pepijat: bash mengembangkan corak tersebut terlebih dahulu, hanya terhadap direktori semasa, jadi find diarahkan untuk mencari satu nama khusus. Dalam direktori dengan satu padanan tunggal, ia secara senyap mencari perkara yang salah. Dengan dua atau lebih padanan, find melaporkan ralat penggunaan, kerana nama tambahan tiba di tempat yang menjangkakan ungkapan.
Peraturannya ringkas. Jika corak itu ditujukan untuk arahan, letakkan tanda petikan. Jika ia ditujukan untuk shell, biarkan ia tanpa tanda petikan. Pembahagian yang sama mengawal corak --exclude dalam tar dan rsync, serta penapis --include dalam grep.
Glob juga bukan ungkapan nalar (regular expressions), walaupun ia berkongsi aksara yang sama. Dalam glob, * bermaksud sebarang rentetan aksara. Dalam ungkapan nalar, * bermaksud sifar atau lebih bagi apa sahaja yang mendahuluinya, jadi grep '*.log' meminta sesuatu yang sangat berbeza daripada apa yang kelihatan.
Pemboleh ubah mengikut tertib operasi yang sama. Globbing berjalan selepas pengembangan pemboleh ubah, jadi pemboleh ubah tanpa tanda petikan yang mengandungi corak akan dikembangkan terhadap direktori.
pat='*.log'
printf '%s\n' $pat
printf '%s\n' "$pat"Tanpa tanda petikan, bash melakukan glob pada hasil pemboleh ubah tersebut. Dengan tanda petikan, corak kekal sebagai literal. Terdapat akibat berkaitan yang perlu anda harapkan: perkataan yang dihasilkan oleh globbing tidak akan dipecahkan lagi pada ruang kosong, jadi for f in *.log mengendalikan nama fail yang mengandungi ruang kosong sebagai satu nama, manakala for f in $(ls *.log) memecahkan nama tersebut kepada dua perkataan. Lakukan gelung (loop) ke atas glob secara terus.
Pratonton corak yang merosakkan sebelum anda menjalankannya
Jangan sekali-kali jadikan rm sebagai arahan pertama yang anda cuba dengan corak baharu. Jalankan corak tersebut dengan sesuatu yang tidak berbahaya, baca senarainya, kemudian tukar hanya arahan di hadapan baris tersebut.
target='!(*.log)'
printf '%s\n' $target
ls -ld -- $target
rm -- $targetMenulis corak ke dalam target sekali sahaja menghalang ketiga-tiga baris daripada terpisah, dan $target sengaja dibiarkan tanpa tanda petikan kerana itulah yang membolehkan bash mengembangkannya. printf '%s\n' mencetak satu nama bagi setiap baris dan tidak menyentuh apa-apa. ls -ld menunjukkan setiap entri itu sendiri dan bukannya menyenaraikan kandungan direktori, dan lajur mod menandakan entri yang mana merupakan direktori, iaitu perkara yang perlu diperiksa sebelum sebarang pemadaman rekursif. Membaca bit kebenaran drwxr-xr-x merangkumi lajur tersebut.
Panggil semula baris sebelumnya dengan anak panah atas dan edit hanya arahannya, supaya corak tersebut kekal sama. Menaip semula corak daripada ingatan adalah punca kesilapan berlaku.
Letakkan -- sebelum corak tersebut. Ia menandakan penghujung pilihan, supaya nama fail yang bermula dengan - dianggap sebagai nama dan bukannya flag.
Jika anda mahu globbing dimatikan untuk sebahagian skrip, set -f menyahdayakannya dan set +f menghidupkannya semula.
Tabiat ini penting kerana rm tidak mempunyai fungsi buat asal (undo). Memulihkan fail yang dipadam dengan rm -rf adalah perlahan dan biasanya tidak lengkap, jadi pratonton jauh lebih murah daripada alternatifnya.
Bersihkan direktori latihan apabila anda selesai. Cetak laluan dan baca dahulu, kerana rm -rf yang dihalakan kepada pemboleh ubah yang salah adalah tepatnya kemalangan yang dibincangkan dalam bahagian ini.
cd ~
echo "$lab"
rm -rf -- "$lab"FAQ
Mengapakah find . -name *.log menemui fail yang salah?
Ini kerana bash mengembangkan *.log sebelum find bermula. Shell memadankan corak tersebut dengan direktori semasa dan menghantar nama fail yang terhasil kepada find, yang kemudiannya mencari nama tersebut pada setiap kedalaman. Jika dua atau lebih fail sepadan, find menerima argumen tambahan di tempat yang sepatutnya terdapat ungkapan, lalu melaporkan ralat penggunaan. Letakkan tanda petikan pada corak sebagai find . -name '*.log' supaya find melakukan pemadanan itu sendiri, mengikut peraturannya sendiri dan rekursinya sendiri.
Apakah maksudnya apabila corak saya muncul sebagai teks literal?
Ini bermakna tiada apa-apa yang sepadan. Secara lalai, bash membiarkan corak yang tidak sepadan tidak berubah dan menghantar aksara mentah kepada arahan, yang kemudiannya menganggapnya sebagai nama fail. Tetapkan shopt -s failglob untuk menukar corak yang tidak sepadan menjadi ralat yang menghentikan arahan, atau shopt -s nullglob untuk menjadikannya berkembang menjadi tiada apa-apa. Di bawah nullglob, pastikan arahan tersebut masih masuk akal tanpa sebarang argumen, kerana ls *.nope menjadi ls kosong dan menyenaraikan keseluruhan direktori.
Bagaimanakah cara saya memadankan setiap fail kecuali satu corak dalam bash?
Aktifkan corak lanjutan dengan shopt -s extglob pada barisnya sendiri, kemudian gunakan bentuk penafian. !(*.log) memadankan nama yang tidak berakhir dengan .log, dan !(*.log|*.md) mengecualikan kedua-duanya. Fail tersembunyi kekal di luar hasil kecuali dotglob turut ditetapkan, dan direktori disertakan, jadi pratonton corak dengan printf '%s\n' !(*.log) sebelum anda menyerahkannya kepada rm.
Mengapakah * melangkau fail tersembunyi?
Titik di hadapan perlu dipadankan secara literal, jadi * tidak akan memadankan nama yang bermula dengan titik. Gunakan .* untuk mencapai nama tersembunyi, atau tetapkan shopt -s dotglob untuk memasukkannya ke dalam setiap corak biasa. Entri . dan .. sentiasa dikecualikan di bawah dotglob, dan bash 5.2 serta versi lebih baharu mengekalkannya di luar .* juga melalui pilihan globskipdots, yang diaktifkan secara lalai.
Adakah saya memerlukan globstar untuk ** berfungsi?
Ya. Tanpa shopt -s globstar, bash menganggap ** sebagai * biasa, yang memadankan di dalam satu direktori dan berhenti di situ. Dengan globstar diaktifkan, ** yang membentuk komponen laluan penuh akan memadankan pada sebarang kedalaman, jadi **/*.log mencapai direktori semasa dan setiap subdirektori di bawahnya, dan **/ secara sendirinya hanya memadankan direktori. Ambil perhatian bahawa ** tidak mengikuti pautan simbolik ke direktori, jadi gunakan find -L untuk pepohon yang dibina daripada symlink.