外部コマンドでのフィルタ処理

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をセットしておく。