版权声明:可以任意转载,但转载时必须标明原作者charlee、原始链接http://tech.idv2.com/2008/09/04/perl-pipe-process-etc/以及本声明。
昨天一个同事问我关于Perl中的 -| 描述符的问题。他的程序大概是这样的:
unless (open FH, "-|") {
exec "foo bar"; # 用exec执行另一个程序
exit;
}
while (<FH>) {
...
}
close FH;
$ret = $? >> 8;
if ($ret == 1) {
...
}那么这里的 open FH, "-|" 是什么意思?$? >> 8 又是什么意思?
关于管道和子进程
首先看看 perldoc -f open,有这样一段话:
If you open a pipe on the command '-', i.e., either '|-' or '-|' with 2-arguments (or 1-argument) form of open(), then there is an implicit fork done, and the return value of open is the pid of the child within the parent process, and 0 within the child process. (Use "defined($pid)" to determine whether the open was successful.) The filehandle behaves normally for the parent, but i/o to that filehandle is piped from/to the STDOUT/STDIN of the child process. In the child process the filehandle isn't opened--i/o happens from/to the new STDOUT or STDIN.
这段话的意思是:如果在'-'上打开一个管道,即通过'|-'或者'-|'并带有两个参数(或一个参数)的形式执行open(),那么就会隐含地执行fork。在父进程中,open的返回值是子进程的pid,而在子进程中open的返回值是0.(使用"defined($pid)"来判断执行是否成功。)对于父进程来说,文件句柄就是普通的文件句柄,但这个文件句柄的输入输出被连接到子进程的STDOUT/STDIN上。在子进程中并不会打开这个文件句柄,输入输出只能在STDOUT或STDIN上发生。
可见,open FH, "-|"一行产生了两个进程。在子进程中open的返回值为0,因此执行下面的 exec "foo bar"语句。注意exec函数执行外部命令后不会返回,而是直接结束进程,所以子进程就到exit之前为止。不过子进程的STDIN/STDOUT被连接到了文件句柄FH上,因此foo bar命令的输出可以在父进程中通过FH来读取。
unless块之后的语句是父进程执行的,通过<FH>读取子进程产生的输出内容。
关于退出状态
那么 $? >> 8 又是什么意思?这个就说来话长了。首先看看 perldoc -f close :
Closing a pipe also waits for the process executing on the pipe to complete, in case you want to look at the output of the pipe afterwards, and implicitly puts the exit status value of that command into $?.
大意是说:关闭管道会等待管道另一端的进程执行结束,并隐含地将这个命令结束时的状态值保存到 $? 中。而现在,FH的另一端是子进程,所以close它会隐含地执行wait,然后将子进程的结束状态保存到 $? 中。 perldoc -f wait中的一句话可以验证这一点:
The status is returned in $?.
那么,$?中保存的“状态”到底是什么呢?我们知道Perl中的wait函数其实是调用系统的 wait() 或waitpid()函数。来看看wait()的文档,man 2 wait:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);可以推测,$? 中保存的值就是这里的 status 值。继续往下看:
If status is not NULL, wait() and waitpid() store status information in the int to which it points. This integer can be inspected with the following macros.
WEXITSTATUS(status): returns the exit status of the child.
如果status不为NULL,wait()和waitpid()将把状态信息保存到status指针指向的整数。该整数可以用下面的宏来查看:
WEXITSTATUS(status):返回子进程的退出状态。
可见,对status(在Perl中就是 $?)使用 WEXITSTATUS宏,才能得到子进程真正的退出状态(即子进程中通过exit()函数或在main()中return时设置的返回值)。
而这个WEXITSTATUS在 /usr/include/bits/waitstatus.h 中有定义:
/* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
#define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)/usr/include/stdlib.h 中再次封装:
# define WEXITSTATUS(status) __WEXITSTATUS(__WAIT_INT(status))可见,WEXITSTATUS宏正是在取 status 值的高8位。
到此可以得知, $? >> 8 其实是在获取子进程的退出状态。
总结
可见,上面这个例子是在Perl中执行外部程序,并获取其输出内容的一个好办法。基本的结构再次重复一下:
unless (open FH, "-|") {
# 这里是子进程的部分
exec "command"; # 通过exec命令执行外部程序
exit; # 保证子进程退出
}
while (<FH>) ... # 读取子进程命令的输出结果
close FH;
$ret = $? >> 8; # 取得子进程的返回值
昨天一个同事问我关于Perl中的 -| 描述符的问题。他的程序大概是这样的:
unless (open FH, "-|") {
exec "foo bar"; # 用exec执行另一个程序
exit;
}
while (<FH>) {
...
}
close FH;
$ret = $? >> 8;
if ($ret == 1) {
...
}那么这里的 open FH, "-|" 是什么意思?$? >> 8 又是什么意思?
关于管道和子进程
首先看看 perldoc -f open,有这样一段话:
If you open a pipe on the command '-', i.e., either '|-' or '-|' with 2-arguments (or 1-argument) form of open(), then there is an implicit fork done, and the return value of open is the pid of the child within the parent process, and 0 within the child process. (Use "defined($pid)" to determine whether the open was successful.) The filehandle behaves normally for the parent, but i/o to that filehandle is piped from/to the STDOUT/STDIN of the child process. In the child process the filehandle isn't opened--i/o happens from/to the new STDOUT or STDIN.
这段话的意思是:如果在'-'上打开一个管道,即通过'|-'或者'-|'并带有两个参数(或一个参数)的形式执行open(),那么就会隐含地执行fork。在父进程中,open的返回值是子进程的pid,而在子进程中open的返回值是0.(使用"defined($pid)"来判断执行是否成功。)对于父进程来说,文件句柄就是普通的文件句柄,但这个文件句柄的输入输出被连接到子进程的STDOUT/STDIN上。在子进程中并不会打开这个文件句柄,输入输出只能在STDOUT或STDIN上发生。
可见,open FH, "-|"一行产生了两个进程。在子进程中open的返回值为0,因此执行下面的 exec "foo bar"语句。注意exec函数执行外部命令后不会返回,而是直接结束进程,所以子进程就到exit之前为止。不过子进程的STDIN/STDOUT被连接到了文件句柄FH上,因此foo bar命令的输出可以在父进程中通过FH来读取。
unless块之后的语句是父进程执行的,通过<FH>读取子进程产生的输出内容。
关于退出状态
那么 $? >> 8 又是什么意思?这个就说来话长了。首先看看 perldoc -f close :
Closing a pipe also waits for the process executing on the pipe to complete, in case you want to look at the output of the pipe afterwards, and implicitly puts the exit status value of that command into $?.
大意是说:关闭管道会等待管道另一端的进程执行结束,并隐含地将这个命令结束时的状态值保存到 $? 中。而现在,FH的另一端是子进程,所以close它会隐含地执行wait,然后将子进程的结束状态保存到 $? 中。 perldoc -f wait中的一句话可以验证这一点:
The status is returned in $?.
那么,$?中保存的“状态”到底是什么呢?我们知道Perl中的wait函数其实是调用系统的 wait() 或waitpid()函数。来看看wait()的文档,man 2 wait:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);可以推测,$? 中保存的值就是这里的 status 值。继续往下看:
If status is not NULL, wait() and waitpid() store status information in the int to which it points. This integer can be inspected with the following macros.
WEXITSTATUS(status): returns the exit status of the child.
如果status不为NULL,wait()和waitpid()将把状态信息保存到status指针指向的整数。该整数可以用下面的宏来查看:
WEXITSTATUS(status):返回子进程的退出状态。
可见,对status(在Perl中就是 $?)使用 WEXITSTATUS宏,才能得到子进程真正的退出状态(即子进程中通过exit()函数或在main()中return时设置的返回值)。
而这个WEXITSTATUS在 /usr/include/bits/waitstatus.h 中有定义:
/* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
#define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)/usr/include/stdlib.h 中再次封装:
# define WEXITSTATUS(status) __WEXITSTATUS(__WAIT_INT(status))可见,WEXITSTATUS宏正是在取 status 值的高8位。
到此可以得知, $? >> 8 其实是在获取子进程的退出状态。
总结
可见,上面这个例子是在Perl中执行外部程序,并获取其输出内容的一个好办法。基本的结构再次重复一下:
unless (open FH, "-|") {
# 这里是子进程的部分
exec "command"; # 通过exec命令执行外部程序
exit; # 保证子进程退出
}
while (<FH>) ... # 读取子进程命令的输出结果
close FH;
$ret = $? >> 8; # 取得子进程的返回值