パイプ
Unix(or its variants)のシェルがどうやってるのかは知らないけど (少し前に Artonさんのところで何か見たような記憶はあるが)、 Windowsの場合は大まかに コンソール プロセスを生成して標準ハンドルをリダイレクトする方法 にあるように 入力用、出力用の二つの無名パイプを作る(CreatePipe) → それぞれを起動する子プロセスのための 標準入力、標準出力および標準エラー出力用のハンドルににDuplicateHandle → 得られたリダイレクト用のハンドルを用いて CreateProcess という手順になるので、 複数段のパイプの尻尾から順番にこのハンドルの複写手順を取りながら 必要なプロセスを起動していくのではなかろうかと。 んー cmdline.split('\|').each{|prc| ほげほげ} て感じ?
おー、UNIX も Windows もあまり変わらないなぁ。ちなみに UNIX で cmd1 | cmd2 なことをやる場合、以下のような感じだろうか。
- pipe(2) で無名パイプを作成して、入力、出力のファイルディスクリプタを取得
- cmd1 を実行するために fork(2) する
- cmd2 を実行するために fork(2) する
- 親プロセスは wait/waitpid で子プロセスの終了を待つ
ところで、なんで尻尾からということになるんでしょ。
一応 C のコードとか。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> static void usage(const char* progname); static void die(const char* msg) __attribute__((__noreturn__));; static pid_t create_process(const char* cmd, int in, int out); static void usage(const char* progname) { fprintf(stderr, "usage: %s cmd1 cmd2\n", progname); } static void die(const char* msg) { perror(msg); exit(1); } int main(int argc, char** argv) { const char *cmd1, *cmd2; pid_t pid1, pid2, pid; int fds[2]; if (argc != 3) { usage(argv[0]); exit(2); } cmd1 = argv[1]; cmd2 = argv[2]; if (pipe(fds) == -1) { die("pipe"); } pid1 = create_process(cmd1, STDIN_FILENO, fds[1]); pid2 = create_process(cmd2, fds[0], STDOUT_FILENO); if ((pid = wait(NULL)) == -1) { die("wait"); } if (pid == pid1) { if (waitpid(pid2, NULL, 0) == -1) { die("waitpid"); } } else { if (waitpid(pid1, NULL, 0) == -1) { die("waitpid"); } } return 0; } static pid_t create_process(const char* cmd, int in, int out) { pid_t pid; pid = fork(); if (pid == -1) { die("fork"); } else if (pid == 0) { if (in != STDIN_FILENO) { if (dup2(in, STDIN_FILENO) == -1) { die("dup2"); } } if (out != STDOUT_FILENO) { if (dup2(out, STDOUT_FILENO) == -1) { die("dup2"); } } if (execlp(cmd, cmd, NULL) == -1) { die("execlp"); } } close(in); close(out); return pid; }