Linux【ワイルドカードと正規表現】の違い, 展開の動作 ~ls, grep, findでの具体例の解説~

ワイルドカードと正規表現の違い

ワイルドカードと正規表現は使い方が似ていますので、よく使い方を間違えることもあるかと思います。この記事では、どのように異なるかを具体例を用いて説明したいと思います。

ワイルドカードとは

Linux におけるワイルドカードとは、「任意の文字列」を表す*(アスタリスク)のことのみを指すと思われがちですが、実は他にもあります。

*任意の0文字以上の文字列。
?任意の1文字。
[ ]大カッコ内のいずれかの文字。[ace]であれば a か c か e のどれか。
[0-8]とハイフンを使うと0から8の間のいずれかの数字になる。
[!dfh] とエクスクラメーションマークを使うと d でも f でも h でも無い
任意の1文字を意味する。
{ , }中カッコ内の,(カンマ)で区切られた文字列のいずれか。
{test.txt,hoge*.txt}であればtest.txt もしくは hoge(任意の文字列).txt
がヒットする。

ワイルドカードは主にOS、つまり bash 等のシェルが解釈できるものです

ワイルドカードは基本的にコマンドに渡される前にシェルが『展開』という操作を行い、展開後の結果をコマンドに渡しますので、コマンドがワイルドカードを解釈して使うケースは稀です。

その稀なケースの一例として、find の-nameオプションがあります(後で詳しく述べます)。

正規表現とは ワイルドカードとの置換

正規表現とは、様々なコマンド/アプリケーションで使われる汎用的な表現方法です。アプリケーションの作り込みによって解釈されているのですが、Linux上のアプリに限らず、Windowsアプリ含め、幅広く使われています。なお、Linux やWindows のシェルは正規表現を解釈するようには作られていません。

正規表現とワイルドカードの置換(変換)は以下の表の通りです。

ワイルド
カード
正規表現意味
*.*任意の0文字以上の文字列。
?.任意の1文字。
[ ][ ]大カッコ内のいずれかの文字。-(ハイフン)で範囲指定。
[! ][^ ]大カッコ内以外のいずれかの文字。-(ハイフン)で範囲指定。
{ , }{|}中カッコ内の区切り文字で区切られた文字列のいずれか。
ワイルドカードなら,(カンマ)、正規表現なら|(ヴァーティカルバー)。

ls での検索時の例

Linuxのシェルではワイルドカードが使えます。シェルでワイルドカードを使うと、シェル上で「展開」という動作が行われます。例えば以下のような状態で、

[root@localhost ~]# ls
anaconda-ks.cfg  abc-def.if  abc-def.te
abc-def.fc  abc-def.pp  tmp
[root@localhost ~]#

ls abc* と打ちます。すると以下のように出力されます。

[root@localhost ~]# ls abc*
abc-def.fc  abc-def.if  abc-def.pp  abc-def.te
[root@localhost ~]#

この場合、シェルではワイルドカードを含む文字列 abc* が以下のように展開され、ls に渡されます。

[root@localhost ~]# ls abc-def.fc abc-def.if abc-def.pp abc-def.te

ワイルドカード付き文字列 "abc*" が、この表現に合致するカレントディレクトリ内のファイルに変換されることを「展開」と呼びます。bash上でコマンドへの引数にワイルドカードを使うと必ずこのような挙動になります。

例えば、ls コマンドの結果を grep にて abc* で引っ掛けてみます。

[root@localhost ~]# ls | grep abc*
[root@localhost ~]#

何も引っ掛かりませんでした。このとき何が起きているのでしょうか。まず、前回と同様、bash 上で以下のように展開されます。

[root@localhost ~]# ls | grep abc-def.fc abc-def.if abc-def.pp abc-def.te

あとは grep関数の解釈次第なのですが、grep では第1引数はパターン(検索文字列)、第2引数以降は検索対象ファイルを意味します。そして第2引数がある場合、標準入力は無視されます。

ところが、正規表現に倣って .* を使うと以下のようになります。

[root@localhost ~]# ls | grep abc.*
abc-def.fc
abc-def.if
abc-def.pp
abc-def.te
[root@localhost ~]#

これは、bash上では . (ドット)は通常の文字列であり、abc. で始まるファイルはカレントディレクトリに存在せず、*(ワイルドカード)は展開されないためです。

展開されない場合、* はそのまま文字列として扱われます。つまり、grep には abc.* という文字列が第1引数として渡されます

grep はワイルドカードには対応していませんが、正規表現を理解するため、.* を任意の文字列として扱います。そして引数が1つだけなので、パイプからの標準入力を受け付けます。結果、ls により出力されるファイル名の中から abc で始まるファイルのみを抽出します。

その証拠に、 ls | grep abc.\* でも同じ結果になります。\ は直後の1文字をエスケープし、単なる文字列として扱う特殊文字です。

もう1つの証拠として、abc-def.ftxt というファイルを作り、中身に "abc-def.fchogehoge" という文字列を保存します。そして、ls | grep abc-def.f* と打つと以下のようになります。

[root@localhost ~]# cat abc-def.ftxt
abc-def.fchogehoge
[root@localhost ~]# ls | grep abc-def.f*
abc-def.fchogehoge
[root@localhost ~]#

これは、 ls | grep abc-def.fc abc-def.ftxt と展開され、(第2引数の)abc-def.ftxt というファイルの中身から (第1引数の)abc-def.fc という文字列を検索した結果です。ls からパイプで渡される標準入力が無関係であることが分かります。

find での名前検索時の例

find コマンドの場合はさらに特殊・複雑です。

ファイル名の検索時に -name オプションが使われますが、-name の後の文字列は find コマンドの中で fnmatch コマンドに渡されます。

fnmatch コマンドの引数には検索文字列が入るのですが、この文字列にはワイルドカードが使えます。シェルでは無くアプリ(コマンド)がワイルドカードに対応しているのです。

ですが厄介なのが、find -name の後の文字列は、そのままではシェルの機能によりワイルドカードが展開されてしまう可能性があります。

例えば、以下のようなディレクトリ構成があったとします。

[root@localhost test]# ls
abc-def.fc  test2
[root@localhost test]# ls test2
test.fc
[root@localhost test]#

この状態において、find . -name *.fc と打ちます。

[root@localhost test]# find . -name *.fc
./abc-def.fc
[root@localhost test]#

abc-def.fc のみが表示されました。test2 配下の test.fc は表示されません。ところが、 abc-def.fc を削除すると test.fc が表示されるようになります。

[root@localhost test]# rm abc-def.fc
rm: 通常の空ファイル `abc-def.fc' を削除しますか? y
[root@localhost test]# find . -name *.fc
./test2/test.fc
[root@localhost test]#

これは、最初のケースでは*が展開され、find . -name abc-def.fc が実行されるのですが、カレントディレクトリにある abc-def.fc が削除されると、展開ができなくなり、* が単なる文字列として扱われ、それが fnmatch に渡されるからです。

fnmatchに確実にワイルドカードとして渡すには、やはり \ でエスケープします。

[root@localhost test]# ls
abc-def.fc test2
[root@localhost test]# ls test2
test.fc
[root@localhost test]# find . -name \*.fc
./test2/test.fc
./abc-def.fc
[root@localhost test]#

シェアする

  • このエントリーをはてなブックマークに追加

フォローする