UNIX/Linuxの部屋 用語集:ファイルグロブ

TOP UNIX/Linuxの部屋 UNIX/Linuxコマンド一覧 用語集 新版 由来/読み方辞書 環境変数マニュアル Cシェル変数 システム設定ファイル システムコール・ライブラリ ネットワークプログラミングの基礎知識 クラウドサービス徹底比較・徹底解説




用語集 ファイルグロブ ファイル名の置換 (メタキャラクタ・ワイルドカード) このエントリーをはてなブックマークに追加

最終更新


UNIX/Linux のシェル sh・bash・csh・tcsh で使うことができるファイルグロブ・ワイルドカードの使い方を説明する。ファイルグロブ・ワイルドカードとは、シェルが * ? {} [] ~ などの文字列を解釈し、ファイル名として展開することである。なお、正規表現と似てはいるが、別物である。



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

ファイルグロブ・ワイルドカードの基本
まずは下記を覚えよう。ファイルグロブ・ワイルドカードの処理はシェルの仕事であるため、使っているシェル (sh・bash・csh・tcsh など) によって細かな違いはあるが、下記にあげたものはおおむねどのシェルでも使えると思われる。
  • ? 任意の 1文字。ただし / は除く
  • * 任意の文字列。空文字も含む。ただし / は除く。単体で使われた場合は、. から始まるファイルにはマッチしない。
  • [...] 角括弧内の 1文字。[abc] は a か b か c にマッチする。[a-z] のように範囲指定もできる。[^a-z] で a-z 以外、となる。
  • ~ 先頭でチルダが使われた場合、ユーザのホームディレクトリに置換される。~user という書き方もできる。

以下に基本的な例をあげる。
% ls *
カレントディレクトリにあるファイル・ディレクトリすべて。ただし . から始まるファイルは除く
% ls .*
カレントディレクトリにある . から始まるファイル・ディレクトリすべて。. や .. も含む。
% 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

"?" や "*" が「/ にマッチしない」とはどういうことかというと、もし "/" にマッチするとした場合、
% ls *
は、サブディレクトリも含めたあらゆるファイル・ディレクトリにマッチする、となってしまう。それでは使いづらいため、"?" や "*" は 1つのディレクトリ内でのファイル名・ディレクトリ名にマッチする、とされている。

グロブ展開はシェルの仕事
ファイル名の展開は 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 を作成するには jot コマンドや seq コマンドを使えばよい。
% touch `jot -w file%03d.txt 999`
→ FreeBSD や macOS なら jot コマンドを使う。
% touch `seq -f'file%03.0f.txt' 1 999`
→ Linux なら seq コマンドを使う。

応用
以上が基本であるが、使いこなすのはなかなか難しい。わかっているつもりの初心者が実践できないのは「組み合わせる」こと。試行錯誤してがんばってほしい。「わからなくなったら 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 対象外にしたりしてみる