# ワイルドカードってなに? - シェルもどきをgoで自作する #11 ## おさらい これまで - [シェルってなに?コマンドラインインタプリタってなに? - シェルもどきをgoで自作する#1](https://hackmd.io/@jyami/HJzohRn2D) - [コマンドと引数の分解、環境変数PATHから探索、外部コマンドと内部コマンド - シェルもどきをgoで自作する #2](https://hackmd.io/@jyami/HyeSkkThP) - [字句解析その1 - シェルもどきをgoで自作する #3](https://hackmd.io/@jyami/Hk3bWSMQO) - [字句解析その2 - シェルもどきをgoで自作する #4](https://hackmd.io/@jyami/S1BkltxQu) - [リダイレクションってなに? - シェルもどきをgoで自作する #5](https://hackmd.io/@jyami/S113NQx8u) - [リダイレクションの種類 - シェルもどきをgoで自作する #6](https://hackmd.io/@jyami/rJ3XmClqd) - [リダイレクションの構文 - シェルもどきをgoで自作する #7](https://hackmd.io/@jyami/BJ04J2Upd) - [コマンドプロセスを作成する際のファイルディスクリプタの操作 - シェルもどきをgoで自作する #8](https://hackmd.io/@jyami/Hy7nSciMt) - [構文解析と抽象構文木 - シェルもどきをgoで自作する #9](https://hackmd.io/@jyami/ByrK1ajIK) - [パイプってなに? - シェルもどきをgoで自作する #10](https://hackmd.io/@jyami/SkXR3iltK) シェルもどき「[oreshell](https://github.com/jyami/oreshell)」を自作している。 前回はパイプの実装をした。 今回はワイルドカードの実装、動作確認。 ## ワイルドカードとは > シェルにはワイルドカードと呼ぶ,ファイル名を補完する機能がある. ワイルドカードを使ってパターンを指定し,長いファイル名を短いタイプでマッチさせたり,複数のファイル群にマッチさせたりすることができる. これをシェルのファイル名展開またはファイル名置換という [参考](http://www.edu.tuis.ac.jp/~mackin/java/2008/linux/wildcard.html) 以下にbashでファイル名の先頭が「a」で始まるファイルの内容を一度にcatコマンドで表示する例を示す。 ```bash $ ls * a.txt a1.txt b.txt $ cat a.txt hoge $ cat a1.txt fuga $ cat b.txt hige $ cat a*.txt hoge fuga ``` シェルがカレントディレクトリに対してパターンマッチ「a*.txt」でファイル群を検索し、マッチしたファイル名一覧を「展開」したあと、catコマンドに引数を渡している。 ### パターンマッチで使う文字とその意味 「\*」は任意の0文字以上の文字列を意味する。 つまり「a*.txt」は 「 「a」で始まり「.txt」で終わるが、その間の文字種は(ある範囲内で)自由で長さも自由 」 というパターンマッチを意味する。 他にも「?」「[」「]」などを指定できる。 ### ファイル名展開の結果 bashがパターンマッチしてファイル名一覧を取得した後にどんな一覧を展開するかは、事前に「set -x」オプションを実行しておくとわかる。 ```bash $ set -x $ cat a*.txt + cat a.txt a1.txt hoge fuga ``` catコマンドに2つの引数「a.txt」「a1.txt」を渡していることがわかる。 では、パターンマッチにヒットしない場合はどうなるか。 いま、カレントディレクトリには「c」で始まる名前のファイルは存在しない(とする)。 パターンマッチ「c*.txt」を指定すると、 ```bash $ cat c*.txt + cat 'c*.txt' cat: 'c*.txt': そのようなファイルやディレクトリはありません ``` となる。 ### 何が起きているのか おおよそ以下のようなことが起きている。 ![](https://i.imgur.com/xB8yExZ.png) パターンマッチにヒットしない場合は、指定した文字列(先の例の場合だと「c*.txt」)をそのままプロセス作成時に使う。 ![](https://i.imgur.com/7P7nfaO.png) ### bashでは「Filename Expansion」と呼ぶ [gnuのbashのマニュアル](https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents)ではワイルドカードによるファイル名展開が「[Filename Expansion](https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html)」という名前になっている。 bashには他にも展開(Expansion)がある。どんな展開があるか知りたい人は[コチラ](https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html)。 "tilde"、"Shell Parameter"などなんとなく「ああ、あれかな」と予想できそうだがここでは割愛。 ## oreshellに実装する ### パターンマッチの実現方法と仕様 パターンマッチをゼロから作るとめちゃくちゃめんどくさいのでできれば作りたくない。 golangのfilepath.Glob()でおおよそ似たようなことができるのでoreshellではこれを使う。 [filepath.Glob()のパターンマッチの仕様](https://cs.opensource.google/go/go/+/go1.17.8:src/path/filepath/match.go;l=44)は以下の通り。 ``` Match reports whether name matches the shell file name pattern. The pattern syntax is: pattern: { term } term: '*' matches any sequence of non-Separator characters '?' matches any single non-Separator character '[' [ '^' ] { character-range } ']' character class (must be non-empty) c matches character c (c != '*', '?', '\\', '[') '\\' c matches character c character-range: c matches character c (c != '\\', '-', ']') '\\' c matches character c lo '-' hi matches character c for lo <= c <= hi Match requires pattern to match all of name, not just a substring. The only possible returned error is ErrBadPattern, when pattern is malformed. ``` ただ、現時点ではfilepath.Glob()のパターンマッチの「全」仕様がoreshell動作時に正しく動作するかは(めんどくさいので)確認しない。 今回は「*」が使えるかどうかだけ確認する。 ### 修正したoreshellのコード [コチラ](https://github.com/masaki-linkode/oreshell/commit/2d3bafc3d84fac27d219caf6f764141e1add7e69) ## つまづいたところ、ハマったところ ### a)エスケープ、クォートをいつ除去するのか 一度実装して動作確認をしているとき、 ```bash $ cat a*.txt hoge fuga ``` と、期待した通りに動いたが パターンマッチをクォートでくくると ```bash $ cat "a*.txt" hoge fuga ``` となった。 期待していた動作は ```bash $ cat "a*.txt" cat: 'a*.txt': そのようなファイルやディレクトリはありません ``` である。 デバッグ調査したところ、構文解析して評価を行う時に 1. エスケープ、クォートの除去 1. ワイルドカードによるファイル名の展開を行う 1. プロセス作成 という実装になっていることがわかった。 これを 1. ワイルドカードによるファイル名の展開を行う 1. 展開されなかった文字列からエスケープ、クォートの除去 1. プロセス作成 の順番に変更すると期待した動作になった。 [該当箇所](https://github.com/jyami/oreshell/commit/2d3bafc3d84fac27d219caf6f764141e1add7e69#:~:text=func%20ExpandFilename(src%20string)%20%5B%5Dstring%20%7B) ■補足 なぜ「エスケープ、クォートの除去」を行うか。 例: ```bash $ grep "ab.*" * ``` 1つ目の引数のクォートの内側の「ab.*」をgrepに渡したい。 しかし、「ab.\*」をシェルのパターンマッチに渡したくない。 その場合は上記のようにクォートでくくる必要がある。(grepに渡すときにはクォートを除去する必要がある。) 2つ目の引数である「\*」はクォートしていないためシェルのパターンマッチの対象になる。 ■この修正後に気づいたこと 本資料を書いているときに[gnuのbashのマニュアルのShell-Expansions](https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html#Shell-Expansions)に、expansionがずらりと並んでいて最後に[Quote Removal](https://www.gnu.org/software/bash/manual/html_node/Quote-Removal.html)とある。 中を読んでみると > After the preceding expansions, all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed. > 上記の展開の後、上記の展開のいずれかの結果ではない、引用符で囲まれていない文字 「\」 、 「」 、および 「"」 がすべて削除されます。[(みらい翻訳)](https://miraitranslate.com/trial/#en/ja/After%20the%20preceding%20expansions%2C%20all%20unquoted%20occurrences%20of%20the%20characters%20%E2%80%98%5C%E2%80%99%2C%20%E2%80%98'%E2%80%99%2C%20and%20%E2%80%98%22%E2%80%99%20that%20did%20not%20result%20from%20one%20of%20the%20above%20expansions%20are%20removed.) 自分が悩んだ「エスケープ、クォートをいつ除去するのか」の答えが書いてあった。orz ### b)どの部分がワイルドカードによるファイル名展開の対象となるのか oreshellの修正をしている間ずっと 「 ワイルドカードによるファイル名展開の対象はコマンドの引数だけ 」 と勝手に思いこんでいた。 いったん実装が終わった後にいろいろ調べてみて、コマンド名もワイルドカードによるファイル名展開の対象ということがわかった(というか、そうであることを思い出した)。 ![](https://i.imgur.com/P647PdA.png) コマンド名をワイルドカードによるファイル名展開する例(bash): ```bash $ set -x $ /bin/fi* + /bin/file /bin/finalrd /bin/fincore /bin/find /bin/findmnt /bin/finalrd: POSIX shell script, ASCII text executable /bin/fincore: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ddd138508cac9a9a8f442bf1ccd1e9a6259bba5e, for GNU/Linux 3.2.0, stripped /bin/find: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b8b9756abacab10f704aec42954e3fd2292f1e85, for GNU/Linux 3.2.0, stripped /bin/findmnt: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=53f49534d008a497eae7eaf3ca15d0ace6053b74, for GNU/Linux 3.2.0, stripped ``` /binに「file」というコマンドがある(linux/ubuntu202004)。「file」コマンドは引数として指定したファイルがどんな種類のファイルなのかを調べて表示する機能を持つ。 /binには「fi」で始まる名前のコマンドが他にも「finalrd」「fincore」「find」「findmnt」がある。 「/bin/fi*」をシェルで実行すると、シェルはワイルドカードによるファイル名の展開を行い、「/bin/file /bin/finalrd /bin/fincore /bin/find /bin/findmnt」という文字列にする。 シェルはこの文字列を元にプロセスの作成をカーネルに依頼する。 ![](https://i.imgur.com/vJgpl2A.png) コマンド名をワイルドカードによるファイル名展開して**何がうれしいのか、どんなメリットがあるのかはさっぱりわからない**。。。 が、この時点のoreshellは上記と同じ結果にならなかったので、コマンド名もワイルドカードによるファイル名展開の対象となるようにoreshellを修正した。 ## 動作確認 ``` (ore) > ls * a.txt a1.txt b.txt (ore) > cat a.txt hoge (ore) > cat a1.txt fuga (ore) > cat b.txt hige (ore) > cat a*.txt hoge fuga (ore) > cat c*.txt cat: 'c*.txt': そのようなファイルやディレクトリはありません ``` ``` (ore) > cat "a*.txt" cat: 'a*.txt': そのようなファイルやディレクトリはありません ``` ``` (ore) > /bin/fi* /bin/finalrd: POSIX shell script, ASCII text executable /bin/fincore: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ddd138508cac9a9a8f442bf1ccd1e9a6259bba5e, for GNU/Linux 3.2.0, stripped /bin/find: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b8b9756abacab10f704aec42954e3fd2292f1e85, for GNU/Linux 3.2.0, stripped /bin/findmnt: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=53f49534d008a497eae7eaf3ca15d0ace6053b74, for GNU/Linux 3.2.0, stripped ``` ## 参考 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