在exec()中重定向STDOUT和STDERR ......没有shell

时间:2017-07-27 16:23:26

标签: perl exec stdout stdin stderr

我正在尝试使用Perl5来fork()子进程。子进程应exec()另一个程序,将其STDIN重定向到命名管道,将STDOUTSTDERR重定向到日志文件。父进程继续在循环中运行,使用waitpid并检查$?以重新启动子进程,以防子进程以非零退出状态死亡。

exec()函数的Perl文档说:

  

如果LIST中有多个参数,则使用LIST中的参数调用 execvp(3)。如果LIST中只有一个元素,则检查参数是否为shell元字符,如果有,则将整个参数传递给系统的命令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' );

所以,我的问题是:如何在不使用shell的情况下重定向我的子命令的STD*文件?

2 个答案:

答案 0 :(得分: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的唯一可靠方法。)

答案 1 :(得分:4)

以下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

但是,不仅仅是解决这个问题(正如格雷格肯尼迪所做的那样),而是将其他问题保持不变(例如,错误地报告ls无法从ls发出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); } ),我就是这样做的。将告诉你解决所有问题:

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

虽然这为您节省了一百行代码,open3仍然是一个非常低级别的代码。 (如果你不得不处理两个管道,你会遇到问题。)我推荐IPC::Run3(更简单)或IPC::Run(更灵活)。

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

 protected function configureListFields(ListMapper $listMapper)
{

     $result = $this->getConfigurationPool()->getContainer()->get('Doctrine')->getRepository('UserBundle:User')->findClient();

    $listMapper

        ->add('client', 'sonata_type_model', array(
            'empty_value' => '', 
            'choice_list' => $result))

              ;
}