Linux基礎

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
がヒットする。

ワイルドカードは Linux では主に bash 等のシェルで使われますが、一部のコマンド/アプリケーションでも使われます

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 ~]# span class=bold-red>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]#

コメント

タイトルとURLをコピーしました