13.3 将输出送往popen
看过捕获外部程序输出的例子后,我们再来看一个将输出发送到外部程序的示例程序popen2.c,它将数据通过管道送往另一个程序。我们在这里使用的是od(八进制输出)命令。
实 验 将输出送往外部程序
我们可以看到,下面这个程序popen2.c非常类似于前面的示例程序,唯一的不同是这个程序是将数据写入管道,而不是从管道中读取。
运行这个程序时,我们将看到如下所示的输出结果:
实验解析
程序使用带有参数“w”的popen启动od-c命令,这样就可以向该命令发送数据了。然后它给od-c命令发送一个字符串,该命令接收并处理它,最后把处理结果打印到自己的标准输出上。
在命令行上,我们可以用下面的命令得到同样的输出结果:
13.3.1 传递更多的数据
我们目前所使用的机制都只是将所有数据通过一次fread或fwrite调用来发送或接收。有时,我们可能希望能以块方式发送数据,或者我们根本就不知道输出数据的长度。为了避免定义一个非常大的缓冲区,我们可以用多个fread或fwrite调用来将数据分为几部分处理。
下面这个程序popen3.c通过管道读取所有数据。
实 验 通过管道读取大量数据
在这个程序中,我们从被调用的进程ps ax中读取数据。该进程输出的数据有多少事先无法知道,所以我们必须对管道进行多次读取。
为简洁起见,我们对程序的输出做了一些修改,如下所示:
实验解析
这个程序调用popen函数时使用了“r”参数,这与popen1.c程序的做法一样。这次,它连续从文件流中读取数据,直到没有数据可读为止。注意,虽然ps命令的执行要花费一些时间,但Linux会安排好进程间的调度,让两个程序在可以运行时继续运行。如果读进程popen3没有数据可读,它将被挂起直到有数据到达。如果写进程ps产生的输出超过了可用缓冲区的长度,它也会被挂起直到读进程读取了一些数据。
在本例中,你可能不会看到Reading:-信息的第二次出现。如果BUFSIZ的值超过了ps命令输出的长度,这种情况就会发生。一些(最新的)Linux系统将BUFSIZ设置为8 192或更大的数字。为了测试程序在读取多个输出数据块时能够正常工作,你可以尝试每次读取少于BUFSIZ个字符(比如BUFSIZE/10个字符)。
13.3.2 如何实现popen
请求popen调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。这有两个效果,一个好,一个不太好。
在Linux(以及所有的类UNIX系统)中,所有的参数扩展都是由shell来完成的。所以,在启动程序之前先启动shell来分析命令字符串,就可以使各种shell扩展(如*.c所指的是哪些文件)在程序启动之前就全部完成。这个功能非常有用,它允许我们通过popen启动非常复杂的shell命令。而其他一些创建进程的函数(如execl)调用起来就复杂得多,因为调用进程必须自己去完成shell扩展。
使用shell的一个不太好的影响是,针对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每个popen调用将多启动两个进程。从节省系统资源的角度来看,popen函数的调用成本略高,而且对目标命令的调用比正常方式要慢一些。
我们用程序popen4.c来演示popen函数的行为。这个程序对所有popen示例程序的源文件的总行数进行统计,方法是用cat命令显示文件的内容并将输出通过管道传递给命令wc-l,由后者统计总行数。如果是在命令行上完成这一任务,我们可以使用如下命令:
事实上,输入命令wc -l popen*.c更简单而且更有效率,但我们是为了通过这个例子来演示popen函数的工作原理。
实 验 popen启动Shell
这个程序使用上面给出的命令,但是通过popen来读取命令输出的结果:
运行这个程序时,我们将看到如下所示的输出结果:
实验解析
这个程序显示,shell在启动后将popen*.c扩展为一个文件列表,列表中的文件名都以popen开头,以.c结尾,shell还处理了管道符(|)并将cat命令的输出传递给wc命令。我们在一个popen调用中启动了shell、cat程序和wc程序,并进行了一次输出重定向。而调用这些命令的程序只看到最终的输出结果。