popen関数の挙動まとめ
C言語のpopen関数の挙動にいつも混乱させられるので、自分用メモとして挙動をまとめてみる。
popen関数の挙動
popen関数はファイルを開くようなノリでプロセスをopenできる。popenの戻り値としてファイルディスクリプタが返ってくるので、それを使ってopenしたプロセスにread/writeの指示を行うことができる。
混乱の種はこのプロセスに対するread/writeである。これはpopenをコールした側の視点に立って考える必要がある。つまり、readであればopenしたプロセスの標準出力を読み取るし、writeであればopenしたプロセスに標準入力を与えることになる。考えてみれば当然なのだが、popenをコールする側・される側でin/outの関係が逆になっており、それが混乱を招く。
以下に挙動をまとめた表を示す。
popenのフラグ | popenコール側の挙動 | popenでopenされる側の挙動 |
---|---|---|
"r" | fgets関数などを使用してopenしたプロセスの標準出力をreadする。 | 標準出力にデータを吐き出す(かもしれない)。 |
"w" | fputs関数などを使用してopenしたプロセスに標準入力を与える(writeする)。 | 標準入力からデータを受け取る(かもしれない)。 |
サンプルプログラム
以下のサイトを参考にサンプルプログラムを書いてみた。
Readモードでopenする例
#include <stdio.h> #include <stdlib.h> #define BUF 256 int main (int argc, char *argv[]) { FILE *fp; char *cmdline = "ls"; if ((fp=popen(cmdline, "r")) == NULL) { perror ("popen failed"); exit(EXIT_FAILURE); } char buf[BUF]; while(fgets(buf, sizeof(buf), fp) != NULL) { printf("=> %s", buf); } pclose(fp); return 0; }
実行結果は以下の通りである。
shinya@DESKTOP-Q1NSEEU:~/programs/popen% ./a.out => a.out => correct_read.c => correct_write.c => wrong_read.c => wrong_write.c
Writeモードでopenする例
#include <stdio.h> #include <stdlib.h> #define BUF 256 int main (int argc, char *argv[]) { FILE *fp; char *cmdline = "python3"; if ((fp=popen(cmdline, "w")) == NULL) { perror ("popen failed"); exit(EXIT_FAILURE); } char buf[BUF]; char* data1 = "print(\"Hello\")\n"; char* data2 = "quit()\n"; fputs(data1, fp); fputs(data2, fp); pclose(fp); return 0; }
実行結果は以下の通りである。
shinya@DESKTOP-Q1NSEEU:~/programs/popen% ./a.out Hello
バグについて
マニュアルを見ると、popenには何やらバグがあるらしい。端的に言うと、readモードでopenしたプロセスの標準入力、及びwriteモードでopenしたプロセスの標準出力の扱いがザルということである。
以下にこのバグを踏むプログラムの例を示す。
Readモードでopenしたプロセスと標準入力を共有してしまう例
#include <stdio.h> #include <stdlib.h> #define BUF 256 int main (int argc, char *argv[]) { FILE *fp; char *cmdline = "python3"; if ((fp=popen(cmdline, "r")) == NULL) { perror ("popen failed"); exit(EXIT_FAILURE); } int data0 = 0; scanf("%d", &data0); printf("input data = %d\n", data0); pclose(fp); return 0; }
実行結果は以下の通りである。
shinya@DESKTOP-Q1NSEEU:~/programs/popen% ./a.out Python 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 10 input data = 10 ★標準入力がpopenした側のプロセスに吸われた >>>
Writeモードでopenしたプロセスと標準出力を共有してしまう例
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUF 256 int main (int argc, char *argv[]) { FILE *fp; char *cmdline = "ls"; printf("Hello "); if(argc == 2 && strncmp(argv[1], "-f", 3) == 0) { fflush(stdout); } if ((fp=popen(cmdline, "w")) == NULL) { perror ("popen failed"); exit(EXIT_FAILURE); } char buf[BUF]; pclose(fp); printf("world!\n"); return 0; }
実行結果は以下の通りである。
shinya@DESKTOP-Q1NSEEU:~/programs/popen% ./a.out a.out correct_read.c correct_write.c wrong_read.c wrong_write.c Hello world! ★"Hello "を先に出力したはずなのにlsコマンドの出力が先に出てしまった
なお、後者についてはpopenの前にfflush関数をコールすることで回避できるようである。上で示したプログラムに"-f"オプションを付けて実行するとこの挙動になる。
shinya@DESKTOP-Q1NSEEU:~/programs/popen% ./a.out -f Hello a.out correct_read.c correct_write.c wrong_read.c wrong_write.c world! ★"Hello "が先に出力された
まとめ
以上、popen関数の挙動についてまとめた。これでもう混乱することはないだろう。