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

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

この記事でできるようになること

  • `*` `?` `[]` の 3 つの基本記号を使い分けられる
  • グロブを展開しているのがシェルであることを説明できる
  • 無マッチ時の挙動・隠しファイル・クォートの落とし穴を避けられる

前提知識(先に読むと理解しやすい記事)

この記事で学べること

言葉の整理(先に取りちがえを防ぎます)

  • グロブ:ファイル名をまとめて指すための書き方です。「ワイルドカード」「パターン」も、ほぼ同じものを指す呼び方です。この記事では「グロブ」にそろえます。
  • 正規表現:文字の中身をさがすための書き方です。grep などで使います。グロブとは別のしくみです。 記号が似ているので取りちがえやすい点に気をつけてください。
  • 展開:あなたが書いた記号を、シェルがほんもののファイル名に置きかえることです。
  • 隠しファイル:名前が . で始まるファイルです。.bashrc が代表例です。

* はグロブでは「何文字でも」という意味です。正規表現では「すぐ前の文字のくりかえし」という意味になります。同じ記号でも意味がちがいます。

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

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

1. グロブとは何か?

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

リナ: 先輩、ls *.txt と書くと .txt のファイルだけ出ますよね。あの * はどういう意味なのでしょうか。
ライニー先輩: いい質問だね。* は「どんな文字でも、何個でもよい」という意味の記号だよ。
ライニー先輩: そして大事なのは、この * を処理しているのが 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 で使う .* などが正規表現にあたります。取りちがえないでください。

先に、事故をふせぐ話

グロブは rm と組み合わせると強力です。同時にあぶなくもあります。rm で消したファイルは、GUI とちがってゴミ箱に行きません。その場ですぐ完全に消えます。

そこで、rm の前に同じパターンを ls で確かめるくせをつけてください。

$ ls *.tmp      # まず何が対象になるかを目で見て確かめる
$ rm *.tmp      # 納得してから消す

本サイトの仮想ターミナルは学習用です。何を打っても、あなたのパソコンのファイルは消えません。安心して試してください。

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 のような隠しファイルは出てきません。これは rm * で設定ファイルまで巻きこんで消す事故をふせぐための決まりです。

3. ? クエスチョンマーク:任意の1文字

結論: ? はちょうど1文字にマッチする。文字数が決まっているファイルを狙うときに使う。

$ ls
log1.txt  log10.txt  log2.txt  log3.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] のように 組み合わせる こともできる。
ライニー先輩: ただし「マッチするのは 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

*[] は、あてはまるファイルが 1 つも無いとき はパターンがそのまま文字として残ります。これが 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 は、そのシェルだけの設定です。ターミナルを閉じるともとに戻ります。

今すぐもとに戻したいときは shopt -u extglob を実行します。

いつでも使いたいときは ~/.bashrc に書き足します。設定ファイルを触るので、先にコピーを取ってください。

$ cp ~/.bashrc ~/.bashrc.bak
$ echo 'shopt -s extglob' >> ~/.bashrc
$ source ~/.bashrc

おかしくなったら cp ~/.bashrc.bak ~/.bashrc で戻せます。>> は末尾に書き足す記号です。> は中身を全部消すので使いません。

7. よくある初心者のつまずき

結論: 無マッチ時の挙動・隠しファイル・クォートの3点が定番の落とし穴。仕様を知れば事故は防げる。

7-1. リナの失敗:マッチしないとパターンがそのまま残る

# .xml ファイルが1つも無い場合
$ ls *.xml
ls: '*.xml' にアクセスできません: そのようなファイルやディレクトリはありません
リナ: エラーメッセージに *.xml がそのまま出ています。* が効いていないのでしょうか。
ライニー先輩: 効いていないのではなく、展開しなかった んだ。
リナ: えっ、展開しないという選択肢があるんですか。
ライニー先輩: そう。bash のはじめの設定では、あてはまるものが 0 件のときはパターンをそのまま文字として渡す。だから ls は「*.xml という名前のファイルが無い」と答えたんだ。
リナ: エラーの読み方が変わりました。0 件だったという合図なんですね。よくわかりました。
ライニー先輩: そのとおり。shopt -s nullglob を使えば、0 件のときは何も渡さない動きに変えられるよ。

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

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

dotglob を有効にすると、* が隠しファイルにもあてはまります。ふだんは無効のままにしておくほうが安全です。

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

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

"*.txt"'*.txt' のように クォート文字列を引用符(' や ")で囲むこと。スペースなどを含む値をひとまとまりとして扱える。で囲むと、グロブ展開は起きません。

* を文字そのものとして使いたいときには便利です。ただし展開したいのに囲ってしまうと、思わぬところで無効になります。

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

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

リナ: 知識は入りました。手を動かして試したいです。
ライニー先輩: いいね、3 問用意したよ。まず練習用ファイルを作ってから挑戦してみて。

練習用のからっぽのディレクトリファイルをまとめて整理する入れ物。Windows や macOS の「フォルダ」と同じもの。を作り、その中で試します。こうすれば、もとからあるファイルにはふれません。

# 練習用ディレクトリとファイルを準備
$ mkdir -p ~/glob-practice && cd ~/glob-practice
$ touch a.txt b.txt c.log data1.csv data2.csv data10.csv

課題 1: .csv で終わるファイルだけを一覧表示しよう。

ヒント 1(方向づけ)を見る

「名前のはじめは何でもよい。ただし終わりは .csv」と伝えます。はじめの部分にあたる記号を 1 つ使います。

ヒント 2(コマンド名)を見る

一覧を出すのは ls です。「何文字でもよい」を表す記号は * です。

答えを見る
$ ls *.csv
data1.csv  data10.csv  data2.csv

.csv で終わる 3 つが出ます。a.txtc.log は外れます。

なお ls は名前を文字として並べます。そのため data10.csvdata2.csv より先に出ます。

課題 2: data + 1 文字 + .csv のファイルだけを表示しよう。data10.csv は除きます。

ヒント 1(方向づけ)を見る

今度は文字の数を決めます。「何文字でもよい」ではなく「ちょうど 1 文字」を表す記号を使います。

ヒント 2(コマンド名)を見る

一覧を出すのは ls です。ちょうど 1 文字を表す記号は ? です。

答えを見る
$ ls data?.csv
data1.csv  data2.csv

data10.csv10 で 2 文字あるので外れます。

課題 3: data1.csvdata2.csv だけを、角カッコを使って表示しよう。

ヒント 1(方向づけ)を見る

えらびたい文字を自分で並べます。「この中のどれか 1 文字」を表す書き方を使います。

ヒント 2(コマンド名)を見る

一覧を出すのは ls です。文字を並べるのは [] です。中に 12 を書きます。

答えを見る
$ ls data[12].csv
data1.csv  data2.csv

[12] は「1 か 2 のどちらか 1 文字」という意味です。data10.csv は外れます。

練習が終わったら、作ったディレクトリごと片づけられます。

-r は「ディレクトリの中身ごと消す」というオプションです。-i を足すと、1 つずつ確認しながら消せます。まず ls で中身を目で見てから消してください。

$ cd ~
$ ls ~/glob-practice
$ rm -ri ~/glob-practice

9. 振り返り

リナ: 整理します。グロブを展開しているのはコマンドではなくシェルなんですね。
ライニー先輩: そのとおり。だからコマンドは、展開されたあとのファイル名を受け取るだけなんだ。
リナ: * は何文字でも、? はちょうど 1 文字、[] は並べた中から 1 文字。ここまで覚えました。
ライニー先輩: 完璧だね。rm と組み合わせるときは、先に同じパターンを ls で確かめること。それだけは忘れないで。

今日の 3 行まとめ

  1. グロブを展開しているのはコマンドではなくシェル。コマンドは展開されたあとの名前を受け取る
  2. * は 0 文字以上、? はちょうど 1 文字、[] は並べた中から 1 文字にあてはまる
  3. rm と組み合わせる前に、同じパターンを ls で確かめる

次に読む

この記事を共有

次の一手