2017-07-27 41 views
1

我正在尝试使用Perl5到fork()一个子进程。子进程应该exec()另一个程序,将其STDIN重定向到一个命名管道,并且STDOUTSTDERR来记录文件。父进程继续以循环方式运行,使用waitpid并检查$?重新启动子进程,以防其死于非零退出状态。对于exec()功能在exec()中重定向STDOUT和STDERR ...没有shell

Perl文档说:

如果在列表中有多个说法,这叫execvp(3)在列表中的参数。如果在LIST中只有一个元素,则检查参数是否为shell元字符,如果有,则将整个参数传递到系统的命令解析器(在Unix平台上为/bin/sh -c,但在其他平台上有所不同)。如果参数中没有shell元字符,它将被拆分为单词并直接传递给execvp,这样更有效。例子:

exec '/bin/echo', 'Your arguments are: ', @ARGV; 
exec "sort $outfile | uniq"; 

这听起来很酷,我想没有一个中介shell中运行我的外部程序,如以下示例所示。不幸的是,我无法将其与输出重定向结合起来(如/bin/foo > /tmp/stdout)。

换句话说,这不起作用:

exec ('/bin/ls', '/etc', '>/tmp/stdout'); 

所以,我的问题是:我怎么重定向STD*文件为我的子命令,而无需使用的壳呢?

回答

5

通过<>重定向是shell功能,这就是为什么它不起作用的原因。你基本上调用/bin/ls并通过>/tmp/stdout只是另一种说法,通过echo更换命令时,这是很容易看到:

exec ('/bin/echo', '/etc', '>/tmp/stdout'); 

打印:

/etc >/tmp/stdout 

通常情况下,你的shell(/bin/sh)会解析该命令发现重定向尝试,打开正确的文件,并修剪参数列表进入/bin/echo

但是 - 程序开始exec()(或system())将继承其调用进程的STDINSTDOUTSTDERR文件。因此,要处理这个正确的方法是

  • 接近每个特殊的文件句柄,
  • 重新打开它们,你需要的日志文件指指点点,
  • 最后调用exec()启动程序。

重写你上面的示例代码,这工作得很好:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec ('/bin/ls', '/etc'); 

...或者,使用perldoc建议的间接对象语法:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec { '/bin/ls' } ('ls', '/etc'); 

(事实上,根据到文档,这个最后的语法是避免在Windows中实例化一个shell的唯一可靠方法。)

4

The follo wing shell命令告诉shell启动/bin/ls,其中/etc用于其STDOUT重定向的参数。

/bin/ls /etc >/tmp/stdout 

在另一方面,下面的Perl语句告诉Perl,以/bin/ls以取代目前的方案,/etc>/tmp/stdout的参数。

exec('/bin/ls', '/etc', '>/tmp/stdout'); 

你没有告诉Perl重定向STDOUT!请记住exec不会启动新进程,因此如果更改子进程的fd 1,则会影响在同一进程中运行ls

但是比起刚刚杀青这一问题(如格雷格·肯尼迪那样),但留下您的其他问题的完整(如误报发射lsls错误的无能),我会告诉你解决这些问题的所有:

use IPC::Open3 qw(open3); 

my $stdout = ''; 
{ 
    # open3 will close the handle used as the child's STDIN. 
    # open3 has issues with lexical file handles. 
    open(local *CHILD_STDIN, '<', '/dev/null') or die $!; 

    my $pid = open3('<&CHILD_STDIN', local *CHILD_STDOUT, '>&STDERR', 
     '/bin/ls', '/etc'); 

    while (my $line = <CHILD_STDOUT>) { 
     $stdout .= $line; 
    } 

    waitpid($pid, 0); 
} 

虽然这为您节省了一百行代码,但open3还是相当低级的。 (如果您必须处理两个管道,则会遇到问题。)我反而推荐使用IPC::Run3(更简单)或IPC::Run(更灵活)。

use IPC::Run3 qw(run3); 
run3([ '/bin/ls', '/etc' ], \undef, \my $stdout); 

use IPC::Run qw(run); 
run([ '/bin/ls', '/etc' ], \undef, \my $stdout); 
相关问题