Unixを使っていると、ちょっと不思議に思う瞬間というのがある。たとえばこんなときだ。
scp remote.host:some/path/* .
このコマンドの意図は明確だ。リモートホストの、ホームディレクトリのとあるディレクトリにあるファイルを全部コピーしたいのだ。だが、これはどうして正しく動作するのだろう。そもそも*
はだれがどのタイミングで展開してくれるのだろうか。
Unixでは、ワイルドカードはshellが開く。コマンドは、ファイル名に展開された後のリストを受け取る。ワイルドカードが100個のファイルにマッチすれば、コマンドは100個のファイルを引数として受け取ることになる。さて問題は上記のscpの場合だ。scpはローカルのファイルシステムではなく、リモートのディレクトリを読み取らなければならない。このような場合はどうなるのだろうか。そもそも、scpを起動するとき、shellはどういう動作をするのだろう。
remote.host:
という記述を見て、リモートをチェックしにいくだろうか? いや、shellはいちいちそんなことはしない。shellは単純に remote.host:some/path/*
というパターンを解釈し、マッチするファイルがローカルにあるか否かを確認する。そして「ない」という結論に至れば、アスタリスクをそのままにして何もせずにscpに渡す。shellはわざわざリモートのファイルを調べに行かない。通信を行うのはscpの仕事で(そのためのsだ)、shellは一切そういう仕事はしない。
マッチしないワイルドカードを放置する動作は、簡単に確認することができる。空のディレクトリで、こうしてみるといい。
$ mkdir empty
$ cd empty
$ echo *
*
ではscpはどうやってワイルドカードを開くのだろうか。実はscpもワイルドカードは処理しない。scpは remote.host:
という記法を見てリモートにssh接続をすると、some/path/*
をコピーコマンドに渡してしまう。このワイルドカードの展開は、リモート上のshellが行う。こうして、ワイルドカードは意図通りに展開される。
このような動作は、ほとんどすべての場合で問題なく動作する。もちろん、万が一パターンにマッチしてしまうファイルがローカルにあると、これはうまくいかない。しかしそのような問題はよっぽどのことがない限り起こらない。Unixは、大抵のケースでうまくいくなら、例外的な問題はあえて無視する方針を取る。これもその例だと思う。
これと似たことは、findを使うときにもよく起こる。findの-name
オプションでは(もちろん)ワイルドカードが使えるが、ついついエスケープしたくなる。だが、実は不要なことがほとんどだ。たとえば;
find . -name x* -delete
のようにしたいとき、カレントディレクトリにxで始まるファイルがないのなら、これは問題なく動作する。shellはx*
を開かずにfindに渡し、findが自力でx*
にマッチするファイルを集めてくれる。
以下余談
MS-DOSにおいては、shellに相当するのはCOMMAND.COMなのだが、これはワイルドカードを開いてくれない。ワイルドカードの展開はコマンドの責任だった。このために、たとえばTurbo CやMS-Cには wildcard.obj のようなワイルドカード展開ライブラリが添付されていて、これをリンクするとargvにあるワイルドカードを開いてくれるという仕様になっていたりした。まあそれはいいのだが、これがリンクされていないコマンドはワイルドカードを無視してしまう。まったく使いづらい仕様だった。
それにMS-DOSのワイルドカードには欠陥もあった。MS-DOSの *
は、8個の ?
と等価という恐ろしい仕様があった。これはつまり
x*
は「xで始まるファイル」という意味で問題ないが、
*x
のように指定しても「xで終わるファイル」という意味にはならなかった。というのも、このような指定は
????????x
と等価であり、9文字目に指定されたxは「文字数オーバー」と見なされ無視されて、全てのファイルとなってしまうのだった(まあ、DOSでは拡張子の奇妙なルールがあるので上記の通りではない。正確なところを説明しても、今の若い人には役に立たないので割愛する。とにかくひどいルールだったということで)。
実のところ、MS-DOSで「xで終わるファイル」にマッチするワイルドカードを書く方法はなく、とっても不便だった。MS-DOSのUnix化において、shell風のワイルドカードを使いたいというのは非常に重要な課題だった。そこで取り組んだのが、コマンドラインのワイルドカードを展開するプログラムの作成だった。たとえばこんな感じだ。
C:\> echo *
*
C:\> w echo *
makefile w.exe wild.c wild.o
w はコマンドライン上に見つけたメタ文字をすべて展開してコマンドをexecする。このとき、cshライクなワイルドカードに対応するので、上記の*x
のようなルールはもちろん、[a-z]
や[^a-z]
も書けた。さらに拡張メタ文字で !file
(マッチするファイル名を除外)とか @file
(ファイルリストの読み込み)などを使えるようにした。
w はシンプルで便利だが、MS-DOSにはもう一つ、コマンドライン長は255文字までという制限があった。ワイルドカードを展開すると、コマンドラインはあっというまに何千文字にも膨れ上がり、すぐに動かなくなってしまう。そこで w には、コマンドラインが255文字以内に収まるようにパースして何度も繰り返し実行する機能を組み込まなければならなかった。これにはさらに難しい問題を引き起こす。コマンドを何度も起動するのはいいが、オプションの指定はすべてのコマンドラインに必要だし、cpの場合あて先も同様にすべてのコマンドラインで指定しなければならない。これらを解析して正しくコマンドラインを構築しなければならないのだ。実に面倒くさい!
まあいろいろルールを作らざるを得ず、へんてこりんなコマンドになったが、ふつーのワイルドカードを使うためにはどうしても必要な工夫だった。