UNIX/Linuxの部屋 用語集:ファイルグロブ ファイル名の置換 (メタキャラクタ・ワイルドカード)


※空白区切りで AND 検索 (例:「ファイル 削除」)

用語集 ファイルグロブ ファイル名の置換 (メタキャラクタ・ワイルドカード)

ファイルグロブとは、シェルが * ? {} [] ~ などの文字列を解釈し、ファイル名として展開することである。なお、グロブは正規表現とは別物である。

使いどころは、「複数のファイルに対して一括して処理を行う」場合である。

基本
以下に基本的な例をあげる。
% ls a*
a から始まるファイル・ディレクトリ
% ls ab?de
ab の後に任意の一文字があり、その後に de が続くファイル・ディレクトリ
% ls *.c
拡張子が c のファイル・ディレクトリ
% ls foo[0-9]*
foo の後に数字が 1つ続き、その後に任意の文字列があるファイル・ディレクトリ
% ls foo[^0-9]*
foo の後に数字以外の文字が 1つ続き、その後に任意の文字列があるファイル・ディレクトリ
% ls ~
ホームディレクトリ
% ls ~/*.txt
ホームディレクトリ直下にある拡張子が txt なファイル・ディレクトリ
% ls ~hoge
ユーザ hoge のホームディレクトリ
% ls foo.{c,h,pl}
foo.c と foo.h と foo.pl

グロブ展開はシェルの仕事

ファイル名の展開は csh や bash などの「シェルが」行うことに注意しよう。ls や grep などの各コマンドが展開するのではない。「シェルが展開し、各コマンドの引数として渡す」のである。

つまりはこういうことだ。
  • ls の引数で使えたファイルグロブは、他のコマンドでも使える (コマンドに依存しない)。
  • ls などの各コマンドは、シェルが展開した「結果だけ」を受け取る。

これが理解できると、find の引数でファイル名を条件とする際に「*」などのメタキャラクタを
% find . -name \*.c -print
% find . -name '*.c' -print
などとエスケープしたりクォートで囲まなければならない理由がわかるだろう。この「*.c」はシェルに解釈させるためのものではなく、find コマンドにそのまま渡す必要があるからだ。なぜなら、もしカレントディレクトリに foo.c と bar.c があるときに、「*」をエスケープせず
% find . -name *.c -print
とすると、シェルは
% find . -name foo.c bar.c -print
と展開し、find コマンドに渡してしまう。これはユーザの意図した挙動ではないだろう。

もしコマンドラインで「*」や「?」の挙動に不可解な点があったら、echo コマンドを使うべし。
% find . -name *.c -print
が意図通りに動かず「おかしいな?」と思ったら、先頭に echo を付けて実行してみる。
% echo find . -name *.c -print
find . -name foo.c bar.c -print
その結果を見て、展開後の文字列が自分の意図通りになっているかどうか確かめること。


グロブは「存在するファイル・ディレクトリ」に展開される。ただし {} は例外。

* や ? などは、必ず存在するファイル・ディレクトリに展開される。しかし {} はファイルが存在しなくても展開されてしまう。

以下、カレントディレクトリにファイルが存在していないと考えてほしい。

% ls *.c
ls: No match.
⇒ シェルは *.c を展開しようとするが、存在しないので、シェルがエラーとする。グロブ展開の段階で失敗したので、ls は実行されない。

% ls foo.{c,h}
ls: foo.c: No such file or directory
ls: foo.h: No such file or directory
⇒ シェルは foo.{c,h} を展開する。ファイルが存在するかどうかはチェックしない。その後 ls foo.c foo.h が実行され、ls がエラーメッセージを表示している。

ファイルグロブの限界

ファイルグロブは便利だが、そのうちファイルグロブだけでは解決できないような問題に突き当たるだろう。例えば以下のような感じだ。
file001.txt〜file999.txt の 999 個のファイルから文字列「foo」を含む行を抽出したいが、file627.txt だけはサイズが 3GB と巨大なため除外したい。

こういう場合は、「広めにマッチさせ、grep -v で削る」というやり方が効果的だ。
% ls file[0-9][0-9][0-9].txt
⇒ まずは file001.txt〜file999.txt をマッチさせ
% ls file[0-9][0-9][0-9].txt | grep -v file627
⇒ file627 を除外し
% grep foo `ls file[0-9][0-9][0-9].txt | grep -v file627`
⇒ その結果から grep する
% grep foo `echo file[0-9][0-9][0-9].txt | grep -v file627`
⇒ 実行結果は変わらないが、内部コマンドの echo を使う方が 1 プロセス節約できる。

実はこの例は、以下のようにすればファイルグロブでも実現可能である。しかし、grep -v と比べ、可読性が低くなることがよくわかるであろう。
% grep foo file{[0-57-9][0-9][0-9],6[013-9][0-9],62[0-689]}.txt

ちなみに file001.txt〜file999.txt を作成するには
% touch `jot -w file%03d.txt 999`
とすればよい。

応用
以上が基本であるが、使いこなすのはなかなか難しい。わかっているつもりの初心者が実践できないのは「組み合わせる」こと。試行錯誤してがんばってほしい。「わからなくなったら echo する」のが基本である。
% grep foo *.c
⇒ これは普通だが…
% grep foo */*.c
⇒ ひとつ下のディレクトリの下の *.c から grep
% grep foo */*/*.c
⇒ さらに下のディレクトリから
% grep foo ../*/*.c
⇒ ひとつ上がって、*/*.c から grep
% grep foo {,*/,*/*/}*.c
⇒ 3 階層まとめて grep。*.c */*.c */*/*.c と同じ
% grep foo {,*/,*/*/}*.[ch]
⇒ さらにヘッダファイルも grep
% grep foo {,*/,*/*/}*.{c,h,cpp}
⇒ cpp も追加
% grep foo *.{c,h,cpp} */*.{c,h,cpp} */*/*.{c,h,cpp}
⇒ 混乱してきたら、展開してみよう
% grep foo `echo {,*/,*/*/}*.{c,h,cpp} | grep -v contrib/`
⇒ contrib/ 以下は grep 対象外にしたりしてみる


頑張って書いたおすすめコンテンツ!