グロブ(ワイルドカード)入門 - * ? [] と extglob の使い分け

グロブ(ワイルドカード)入門 - * ? [] と extglob の使い分け

この記事で学べること

  • グロブ(ワイルドカード) が「シェルの仕事」だと理解できる
  • * ? []3 つの基本記号 を使い分けられる
  • {}(ブレース展開)との違いが分かる
  • extglob・ドットファイル・無マッチ時の挙動まで 落とし穴を避けられる

結論(先に覚えるべき型)

  • どんな文字が何個でも → *(例: *.txt
  • ちょうど1文字 → ?(例: file?.log
  • 限られた候補から1文字 → [](例: img[0-9].png
  • 展開するのは コマンドではなくシェル

1. グロブとは何か?

結論: グロブはシェルがコマンド実行前にワイルドカードをファイル名へ展開する仕組み。コマンドは展開後の結果を受け取る。

リナ: 先輩、ls *.txt って書くと .txt のファイルだけ出ますよね。あの * ってどういう意味なんですか?
ライニー先輩: いい質問だね。*ワイルドカード と呼ばれる記号で、「どんな文字でも、何個でもOK」という意味なんだ。そして大事なのは、この * を処理しているのは ls ではなくシェル だということ。
リナ: え、ls じゃないんですか?
ライニー先輩: そう。あなたが Enter を押した瞬間、シェルが *.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* だと?
ライニー先輩: 「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.txt10 で2文字あるからマッチしないんだ。
リナ: 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}.txt
file1.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
リナ: 基本記号は分かりました。でも「.jpg と .png だけ」みたいな複数条件は書けますか?
ライニー先輩: 標準のグロブだと少し苦しいけど、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 っていうファイルを探してる…? 変なエラーです。
ライニー先輩: bash の既定では、マッチが0件だとパターンを展開せず、そのまま文字列として ls に渡すんだ。だから「*.xml というファイルが無い」と言われる。shopt -s nullglob を使うと、0件のときは空に展開されるよ。

7-2. 隠しファイルは * で拾えない

# 隠しファイルも対象にしたいとき
$ shopt -s dotglob
$ ls *

dotglob を有効にすると * がドットファイルにもマッチする。普段は無効のままが安全。

7-3. クォートするとグロブは無効になる

# クォートすると * は展開されない
$ ls "*.txt"
ls: '*.txt' にアクセスできません: そのようなファイルやディレクトリはありません

"*.txt"'*.txt' のように クォートで囲むとグロブ展開は起きない* を文字そのものとして扱いたいときは便利だが、展開したいのに囲ってしまうと意図せず無効化される。

8. ミニ課題:実際にやってみよう

結論: 拡張子の抽出・文字数指定・範囲指定の3問で、グロブの基本記号を手を動かして確かめる。

リナ: 知識は入りました! 手を動かして試したいです。
ライニー先輩: いいね、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.csvdata2.csv だけを、角カッコを使って表示しよう。

ヒントを見る

12 を候補として [] に並べる。

解答例
$ ls data[12].csv

[12] は「1 か 2 のどちらか1文字」。data10.csv は外れる。

次に読む