これまで
シェルもどき「oreshell」を自作している。
前回はリダイレクションの実装をした。
今回はパイプの実装、動作確認。
Unix系オペレーティングシステムのパイプ(pipe)、もしくはパイプライン (pipeline) とは、複数のプログラムの入出力をつなぐための仕組み(プロセス間通信)の一つである。独立した複数のプログラムを組み合わせる事で、多様かつ複雑なデータ処理を効率よく柔軟に実行できる利点がある。また、現有のソフトウエア資産の再利用が可能になるため、プログラム生産性の面でも利点もある。
wikipedia
$ ls | grep ab | sort
上記のパイプライン例のイメージ図は以下の通り。
上記のパイプライン例では以下の処理を行っている。
上記の「ls」「grep ab」「sort」プロセスはすべて並列で実行し、各プロセスは「入力されたデータを処理して出力する」を逐次行う。
「ls」の実行が終わってから「grep ab」を起動するわけではない。「ls」が少し実行して少し出力するとそれを「grep ab」がパイプ経由で受け取って処理して出力する。
「カレントディレクトリにあるファイル名の一覧をある条件で取得したい」と思ったときに、それ専用のプログラムを書かなくても、上記パイプラインコマンドを実行するだけで取得することができる。
Unix系OSの場合はpipeシステムコールが用意されているのでそれを使って実現するのが一般的らしい。(Linuxのpipeシステムコール)
pipeシステムコールを呼ぶと、「パイプに書き込むための口」と「パイプから読み出すための口」のファイルディスクリプタを取得できる。
goではos.Pipe()があるのでoreshellではこれを使って実現する。
以前にリダイレクションの構文を書いたのでそれを拡張する。
oreshellのパイプの構文は以下の通り。
pipeline_sequence ::= simple_command ( '|' simple_command )*
railroad diagram
「simple_command」を含めて全部くっつけるとこんな感じ。
以前に抽象構文木を書いたのでそれを拡張する。
Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
パイプライン対応による変更点のみ説明。
解析対象の字句として「|」を追加する(説明は省略)。
構文に従って字句解析トークン群から抽象構文木を作成する(説明は省略)。
変更点の内、重要な箇所をピックアップして以下に説明。
package process
(略)
type Pipe struct {
reader *os.File
writer *os.File
}
type Process struct {
command string
argv []string
stdin *os.File
stdout *os.File
redirections *[]ast.Redirection
previous *Process
next *Process
pipe *Pipe
osProcess *os.Process
}
package process
(略)
type PipelineSequence struct {
processes []*Process
}
上記の構造体を図に表すと以下の通り。
Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
「★」がついている要素はパイプライン対応で使う要素である。
これらについてはあとで説明する。
抽象構文木(ast.PipelineSequence)から
package process
(略)
func NewPipelineSequence(pipelineSequence *ast.PipelineSequence) (me *PipelineSequence, err error) {
me = &PipelineSequence{}
for _, sc := range pipelineSequence.SimpleCommands {
p, err := NewProcess(sc)
if err != nil {
return nil, err
}
log.Logger.Printf("Process New %+v\n", p)
me.processes = append(me.processes, p)
}
log.Logger.Printf("processes size %d\n", me.size())
for i, p := range me.processes {
//プロセス数が1つの場合はパイプ設定対象外
//プロセスが複数でも末尾のプロセスはパイプ設定対象外。
if i+1 < me.size() {
log.Logger.Printf("pipe %+v\n", p)
err = p.PipeWithNext(me.processes[i+1])
if err != nil {
return nil, err
}
}
}
return me, nil
}
process.Process#PipeWithNext()でプロセスと次のプロセスとの間にパイプを作成し、
を行う。
また、プロセスと次のプロセスの前後関係を設定する。
package process
(略)
func (me *Process) PipeWithNext(next *Process) (err error) {
me.pipe, err = newPipe()
if err != nil {
return err
}
me.stdout = me.pipe.writer
next.stdin = me.pipe.reader
me.next = next
next.previous = me
return nil
}
(ore) > ls
abc.txt abc2.txt ac.txt
(ore) > ls | grep ab | sort
abc.txt
abc2.txt
oreshellでパイプを実現できた。