これまで
シェルもどき「oreshell」を自作している。
前回はパイプの実装をした。
今回はワイルドカードの実装、動作確認。
シェルにはワイルドカードと呼ぶ,ファイル名を補完する機能がある. ワイルドカードを使ってパターンを指定し,長いファイル名を短いタイプでマッチさせたり,複数のファイル群にマッチさせたりすることができる. これをシェルのファイル名展開またはファイル名置換という
参考
以下にbashでファイル名の先頭が「a」で始まるファイルの内容を一度にcatコマンドで表示する例を示す。
シェルがカレントディレクトリに対してパターンマッチ「a*.txt」でファイル群を検索し、マッチしたファイル名一覧を「展開」したあと、catコマンドに引数を渡している。
「*」は任意の0文字以上の文字列を意味する。
つまり「a*.txt」は
「
「a」で始まり「.txt」で終わるが、その間の文字種は(ある範囲内で)自由で長さも自由
」
というパターンマッチを意味する。
他にも「?」「[」「]」などを指定できる。
bashがパターンマッチしてファイル名一覧を取得した後にどんな一覧を展開するかは、事前に「set -x」オプションを実行しておくとわかる。
catコマンドに2つの引数「a.txt」「a1.txt」を渡していることがわかる。
では、パターンマッチにヒットしない場合はどうなるか。
いま、カレントディレクトリには「c」で始まる名前のファイルは存在しない(とする)。
パターンマッチ「c*.txt」を指定すると、
となる。
おおよそ以下のようなことが起きている。
パターンマッチにヒットしない場合は、指定した文字列(先の例の場合だと「c*.txt」)をそのままプロセス作成時に使う。
gnuのbashのマニュアルではワイルドカードによるファイル名展開が「Filename Expansion」という名前になっている。
bashには他にも展開(Expansion)がある。どんな展開があるか知りたい人はコチラ。
"tilde"、"Shell Parameter"などなんとなく「ああ、あれかな」と予想できそうだがここでは割愛。
パターンマッチをゼロから作るとめちゃくちゃめんどくさいのでできれば作りたくない。
golangのfilepath.Glob()でおおよそ似たようなことができるのでoreshellではこれを使う。
filepath.Glob()のパターンマッチの仕様は以下の通り。
ただ、現時点ではfilepath.Glob()のパターンマッチの「全」仕様がoreshell動作時に正しく動作するかは(めんどくさいので)確認しない。
今回は「*」が使えるかどうかだけ確認する。
一度実装して動作確認をしているとき、
と、期待した通りに動いたが
パターンマッチをクォートでくくると
となった。
期待していた動作は
である。
デバッグ調査したところ、構文解析して評価を行う時に
という実装になっていることがわかった。
これを
の順番に変更すると期待した動作になった。
■補足
なぜ「エスケープ、クォートの除去」を行うか。
例:
1つ目の引数のクォートの内側の「ab.*」をgrepに渡したい。
しかし、「ab.*」をシェルのパターンマッチに渡したくない。
その場合は上記のようにクォートでくくる必要がある。(grepに渡すときにはクォートを除去する必要がある。)
2つ目の引数である「*」はクォートしていないためシェルのパターンマッチの対象になる。
■この修正後に気づいたこと
本資料を書いているときにgnuのbashのマニュアルのShell-Expansionsに、expansionがずらりと並んでいて最後にQuote Removalとある。
中を読んでみると
After the preceding expansions, all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed.
上記の展開の後、上記の展開のいずれかの結果ではない、引用符で囲まれていない文字 「\」 、 「」 、および 「"」 がすべて削除されます。(みらい翻訳)
自分が悩んだ「エスケープ、クォートをいつ除去するのか」の答えが書いてあった。orz
oreshellの修正をしている間ずっと
「
ワイルドカードによるファイル名展開の対象はコマンドの引数だけ
」
と勝手に思いこんでいた。
いったん実装が終わった後にいろいろ調べてみて、コマンド名もワイルドカードによるファイル名展開の対象ということがわかった(というか、そうであることを思い出した)。
コマンド名をワイルドカードによるファイル名展開する例(bash):
/binに「file」というコマンドがある(linux/ubuntu202004)。「file」コマンドは引数として指定したファイルがどんな種類のファイルなのかを調べて表示する機能を持つ。
/binには「fi」で始まる名前のコマンドが他にも「finalrd」「fincore」「find」「findmnt」がある。
「/bin/fi*」をシェルで実行すると、シェルはワイルドカードによるファイル名の展開を行い、「/bin/file /bin/finalrd /bin/fincore /bin/find /bin/findmnt」という文字列にする。
シェルはこの文字列を元にプロセスの作成をカーネルに依頼する。
コマンド名をワイルドカードによるファイル名展開して何がうれしいのか、どんなメリットがあるのかはさっぱりわからない。。。
が、この時点のoreshellは上記と同じ結果にならなかったので、コマンド名もワイルドカードによるファイル名展開の対象となるようにoreshellを修正した。
UNIXのワイルドカード
http://www.edu.tuis.ac.jp/~mackin/java/2008/linux/wildcard.html
gnu bash マニュアル
https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents
Linux【ワイルドカードと正規表現】の違いと変換,展開の動作
https://milestone-of-se.nesuke.com/sv-basic/linux-basic/wildcard-regular-expression/
シェルにおけるワイルドカードの挙動についての整理 - Qiita
https://qiita.com/daisuke0115/items/76136703c90e1f17a1fc