外部コマンドでのフィルタ処理
popenは"r+"とかではオープン出来ないので、自前の処理を作るときのおぼえがき。
pipe(p)と実行したときにセットされるファイルディスクリプタは、p[0]に読み出し用、p[1]に書き込み用の順になっている。
dup2(fd,0);close(fd)の順に実行することでファイルディスクリプタfdを標準入力にリダイレクトしたことになる。
ここでは後でまとめてclose出来るように、親→子パイプをp[0],p[1]、子→親パイプをp[2],p[3]にセットして使うことを仮定。
配列 | 役割 | 親プロセス | 子プロセス |
p[0] | 親→子の読み出し用 | 使わない | 標準入力にリダイレクト |
p[1] | 親→子の書き込み用 | 子への入力をwriteする | 使わない |
p[2] | 子→親の読み出し用 | 子の出力をreadする | 使わない |
p[3] | 子→親の書き込み用 | 使わない | 標準出力にリダイレクト |
上記の方針で書いたサンプルコード。
流れを追いやすくするためにエラー処理は省略してるので注意。
#include<unistd.h> #include<string.h> filter(char **cmd,char *buf,int count){ int p[4],i; pipe(p); pipe(p+2); if(fork()){ write(p[1],buf,strlen(buf)); close(p[1]); read(p[2],buf,count); for(i=0;i<4;i++) close(p[i]); }else{ dup2(p[0],0); dup2(p[3],1); for(i=0;i<4;i++) close(p[i]); execvp(cmd[0],cmd); } } main(){ char *cmd[3],s[256]="1\n3\n2"; cmd[0]="sort"; cmd[1]="-r"; cmd[2]=NULL; filter(cmd,s,256); puts(s); }
read/writeのサイズ引数について
readでは余裕を持たせてバッファ用配列のサイズと同じ値を指定する。通常はEOFになった時点で止まり、この引数が意味を持つのはあふれそうになったときだけ。
writeではきっちり書き込みたいデータの長さと同じにする。バイナリデータを書くこともあるのだから'\0'で処理が止まるようにはなっておらず、必ずこのサイズだけ書き込みが行われるので注意。
forkの返値について
成功した場合、親プロセスには子プロセスの PID が返され、子プロセスには 0 が返される。失敗した場合、親プロセスに -1 が返される。
Cでは0がfalseでそれ以外がtrueなので、上記サンプルでは前半が親プロセス用の処理、後半が子プロセス用の処理になる。
execvpについて
1番目の引数としてコマンド名はすでに与えているが、2番目の引数はargv互換なので、コマンドの引数だけでなくコマンド名自体も渡す必要がある。
argcは渡さないので、ちゃんとargvの最後の目印としてのNULLをセットしておく。