グロブ(ワイルドカード)入門 - * ? [] と extglob の使い分け
この記事で学べること
- グロブ(ワイルドカード) が「シェルの仕事」だと理解できる
*?[]の 3 つの基本記号 を使い分けられる{}(ブレース展開)との違いが分かるextglob・ドットファイル・無マッチ時の挙動まで 落とし穴を避けられる
結論(先に覚えるべき型)
- どんな文字が何個でも →
*(例:*.txt) - ちょうど1文字 →
?(例:file?.log) - 限られた候補から1文字 →
[](例:img[0-9].png) - 展開するのは コマンドではなくシェル
1. グロブとは何か?
結論: グロブはシェルがコマンド実行前にワイルドカードをファイル名へ展開する仕組み。コマンドは展開後の結果を受け取る。
ls *.txt って書くと .txt のファイルだけ出ますよね。あの * ってどういう意味なんですか?* は ワイルドカード と呼ばれる記号で、「どんな文字でも、何個でもOK」という意味なんだ。そして大事なのは、この * を処理しているのは ls ではなくシェル だということ。ls じゃないんですか?*.txt を見て、実際に存在するファイル名に書き換えてから ls に渡しているんだ。これを「グロブ展開」と呼ぶよ。展開のイメージ
カレントディレクトリに a.txt b.txt memo.md がある場合:
あなたが入力: ls *.txt シェルが展開: ls a.txt b.txt ← memo.md は外れる ls が受け取る: a.txt b.txt
ls は * を一切知らない。展開済みのファイル名を受け取るだけ。
グロブはファイル名に対する仕組み。文字列の中身を検索する正規表現(grep で使う .* など)とは 別物 なので混同しないこと。
2. * アスタリスク:0文字以上の任意の文字列
結論:
*は0文字以上の任意の文字列にマッチする。ただし先頭のドットとスラッシュにはマッチしない。
$ ls a.txt b.txt data.csv report.txt notes.md
# 拡張子が .txt のものだけ $ ls *.txt
a.txt b.txt report.txt
*.txt は「何か + .txt」って感じですね。* は 0文字でもマッチする のもポイント。だから * だけ書くと「全部」になるよ。re* だと?report.txt がマッチする。前・後ろ・真ん中、どこにでも置けるよ。# re で始まる $ ls re* report.txt # data を含む(前後に何かあってもOK) $ ls *data* data.csv
* は 先頭のドット(隠しファイル)にマッチしない。ls * をしても .bashrc のような隠しファイルは出てこない。これは「* で .bashrc まで巻き込んで消す」事故を防ぐための仕様。
3. ? クエスチョンマーク:任意の1文字
結論:
?はちょうど1文字にマッチする。文字数が決まっているファイルを狙うときに使う。
$ ls log1.txt log2.txt log3.txt log10.txt
# log + 1文字 + .txt(log10.txt は2文字なので外れる) $ ls log?.txt
log1.txt log2.txt log3.txt
? は1文字だけなんですね。log10.txt が外れたのはなぜですか?log?.txt は「log のあとに きっかり1文字、そして .txt」という意味だから。log10.txt は 1 と 0 で2文字あるからマッチしないんだ。?? と2個並べればいいよ。log??.txt なら log10.txt がマッチする。# 2文字ぶん $ ls log??.txt log10.txt
4. [] 角カッコ:候補の中から1文字
結論:
[]は角カッコ内に並べた文字のどれか1文字にマッチする。範囲指定や否定もできる。
$ ls img0.png img1.png img2.png img9.png imgA.png
4-1. 候補を列挙する
# 0 か 1 か 2 のどれか $ ls img[012].png
img0.png img1.png img2.png
4-2. 範囲で指定する
# 0〜9 の数字1文字 $ ls img[0-9].png
img0.png img1.png img2.png img9.png
[0-9] で「0から9まで」なんですね。便利! アルファベットもできますか?[a-z] で小文字、[A-Z] で大文字、[a-zA-Z0-9] のように 組み合わせ もOK。あくまで「マッチするのは1文字」という点だけ忘れないでね。4-3. 否定する(! または ^)
# 数字「以外」の1文字 $ ls img[!0-9].png
imgA.png
[] の書き方まとめ
| 書き方 | 意味 |
|---|---|
[abc] |
a / b / c のどれか1文字 |
[a-z] |
a〜z の1文字(範囲) |
[0-9] |
0〜9 の1文字(範囲) |
[!0-9] |
数字以外の1文字(否定) |
[a-zA-Z] |
英字1文字(範囲の組合せ) |
5. {} ブレース展開:グロブと混同しやすい別物
結論: ブレース展開はファイルの有無に関係なく文字列を生成する。実在ファイルに展開するグロブとは別の仕組み。
# ブレース展開:3つの文字列を生成する
$ echo file{1,2,3}.txtfile1.txt file2.txt file3.txt
{} は「文字列を作る」だけで、ファイルが実在するかどうかは見ていない。だから存在しないファイル名でも生成される。{} は「これからまとめて作る」用途、* や [] は「すでにあるものを選ぶ」用途、と覚えると分かりやすいよ。# 連番ディレクトリをまとめて作る(実在しなくてOK)
$ mkdir log{2024,2025,2026}
# 連番の範囲も書ける
$ echo {1..5}
1 2 3 4 5* や [] は マッチするファイルが無いとき はパターンがそのまま文字列として残る(bash の既定動作)。一方 {} は常に展開される。挙動が違うので注意。
6. extglob:拡張グロブでもっと柔軟に
結論: extglob を有効にすると「いずれか」「除外」などの高度なパターンが書ける。既定では無効なので shopt で有効化する。
# 拡張グロブを有効化(bash) $ shopt -s extglob
@(...) で「いずれか」、!(...) で「除外」が書けるんだ。extglob の記法
| 書き方 | 意味 |
|---|---|
?(pattern) |
0回か1回 |
*(pattern) |
0回以上 |
+(pattern) |
1回以上 |
@(pattern) |
ちょうど1回(いずれか) |
!(pattern) |
pattern 以外 |
pattern は | で区切って複数指定できる。
# .jpg または .png にマッチ $ ls *.@(jpg|png) # .txt 以外のすべて $ ls !(*.txt)
shopt -s extglob はそのシェルだけの設定。常に使いたいときは ~/.bashrc に追記する。元に戻すときは shopt -u extglob。
7. よくある初心者のつまずき
結論: 無マッチ時の挙動・隠しファイル・クォートの3点が定番の落とし穴。仕様を知れば事故は防げる。
7-1. マッチしないとパターンがそのまま残る
# .xml ファイルが1つも無い場合 $ ls *.xml ls: '*.xml' にアクセスできません: そのようなファイルやディレクトリはありません
*.xml っていうファイルを探してる…? 変なエラーです。ls に渡すんだ。だから「*.xml というファイルが無い」と言われる。shopt -s nullglob を使うと、0件のときは空に展開されるよ。7-2. 隠しファイルは * で拾えない
# 隠しファイルも対象にしたいとき $ shopt -s dotglob $ ls *
dotglob を有効にすると * がドットファイルにもマッチする。普段は無効のままが安全。
7-3. クォートするとグロブは無効になる
# クォートすると * は展開されない $ ls "*.txt" ls: '*.txt' にアクセスできません: そのようなファイルやディレクトリはありません
"*.txt" や '*.txt' のように クォートで囲むとグロブ展開は起きない。* を文字そのものとして扱いたいときは便利だが、展開したいのに囲ってしまうと意図せず無効化される。
8. ミニ課題:実際にやってみよう
結論: 拡張子の抽出・文字数指定・範囲指定の3問で、グロブの基本記号を手を動かして確かめる。
# 練習用ファイルを準備 $ touch a.txt b.txt c.log data1.csv data2.csv data10.csv
課題1: .csv で終わるファイルだけを一覧表示しよう。
ヒントを見る
「何か + .csv」を * で表す。
解答例
$ ls *.csv
data1.csv data2.csv data10.csv が表示される。
課題2: data + 1文字 + .csv のファイルだけを表示しよう(data10.csv は除く)。
ヒントを見る
1文字を表す記号は ?。
解答例
$ ls data?.csv
data1.csv data2.csv だけが残る。data10.csv は2文字なので外れる。
課題3: data1.csv と data2.csv だけを、角カッコを使って表示しよう。
ヒントを見る
1 と 2 を候補として [] に並べる。
解答例
$ ls data[12].csv
[12] は「1 か 2 のどちらか1文字」。data10.csv は外れる。