I tested an example of APUE(Advanced Programming in the UNIX Environment, 3ed), 15.3 popen and plcose Function.
Example code is as below
#include "apue.h"
#include <sys/wait.h>
#define PAGER "${PAGER:-more}" /* environment variable, or default */
int
main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;
if (argc != 2)
err_quit("usage: a.out <pathname>");
if ((fpin = fopen(argv[1], "r")) == NULL)
err_sys("can't open %s", argv[1]);
if ((fpout = popen(PAGER, "w")) == NULL)
err_sys("popen error");
/* copy argv[1] to pager */
while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF)
err_sys("fputs error to pipe");
}
if (ferror(fpin))
err_sys("fgets error");
if (pclose(fpout) == -1)
err_sys("pclose error");
exit(0);
}
And my input file is "temp.in", we have
$ cat temp.in
test sentence
And the output of example is as below
$ ./popen2 temp.in
test sentence
As showed in APUE, executing fpout = popen(cmdstring, "w")
is similiar to its child process execute sh -c cmdstring
. Thus the popen
of code showed in the last section should execte
sh -c "${PAGER:-more} test sentence"
My OS(Ubuntu 22.04.3 LTS) doesn't have an environmental value named PAGER
, thus it should execute
sh -c "more test sentence"
However, order more
's option should be filename as far as I know. Test below order in my system and get the output, which is different from what I inferred above:
$ ${PAGER:-more} temp.in
test sentence
$ ${PAGER:-more} test sentence
more: can't open test: no such file or directory
more: can't open sentence: no such file or directory
What's wrong with my deduce?
Well, the step by step working principle of popen()
is as below(we ignore some irrelevant checks):
First: the currency process call pipe()
to create a pipe
int pfd[2];
pipe(pfd);
Second: the current process call fork()
to create a child process.
if (fork() == 0)
/* child process work */
else if(fork() > 0)
/* parent process work */
Third: the child and parent process close their own fileno to create an unidirectional pipe.
e.d. popen(cmdstring, "r")
, the parent process close its write fileno, and the child process close its read fileno.
/* child process work */
close(pfd[0]); // pfd[0] is read port
/* parent process work */
close(pfd[1]); // pfd[0] is write port
Forth: The parent process and child process redirect their own fileno.
e.d. popen(cmdstring, "r")
, the parent redirect its STDIN_FILENO
to read port, and the child process redirect its STDOUT_FILENO
to write port.
/* child process work */
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
/* parent process work */
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
Then, the child process could write its output to the pipe, and the parent process could read messages from its child process through its stdin.
Back to the problem. popen({PAGER:-more}, "w")
connect the currency process's stdout to more
process's stdin.
Thus fgets(line, MAXLINE, fpin)
read from argv[1]
and transfer the string to more
through pipe, and more
print it to the terminate. The effect of example is simmiliar to
cat $argv1 | ${PAGER:-more}