【问题标题】:Redirect STDOUT and STDERR in exec()... without shell在 exec() 中重定向 STDOUT 和 STDERR..​​. 没有 shell
【发布时间】:2017-07-27 16:23:26
【问题描述】:

我正在尝试使用 Perl5 来 fork() 一个子进程。子进程应该exec() 另一个程序,将其STDIN 重定向到命名管道,并将STDOUT 和STDERR 重定向到日志文件。父进程继续循环运行,使用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* 文件?

【问题讨论】:

    标签: perl exec stdout stdin stderr


    【解决方案1】:

    通过< 和> 重定向是一个shell 功能,这就是它在这种用法中不起作用的原因。您实际上是在调用 /bin/ls 并将 >/tmp/stdout 作为另一个参数传递,在将命令替换为 echo 时很容易看到:

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

    打印:

    /etc >/tmp/stdout
    

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

    然而—— 以exec()(或system())开头的程序将继承其调用进程的STDIN、STDOUT 和STDERR 文件。所以,处理这个问题的正确方法是

    • 关闭每个特殊的文件句柄,
    • 重新打开它们,指向您想要的日志文件,然后
    • 最后调用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 的唯一可靠方法。)

    【讨论】:

      【解决方案2】:

      以下 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在同一个进程中运行。

      但不是仅仅解决这个问题(就像 Greg Kennedy 所做的那样),而是让您的其他问题完好无损(例如,将无法启动 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);
      }
      

      虽然为您节省了一百行代码,但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);
      

      【讨论】:

        猜你喜欢
        • 2014-07-22
        • 1970-01-01
        • 2012-10-08
        • 1970-01-01
        • 2012-08-07
        • 1970-01-01
        • 1970-01-01
        • 2014-10-23
        • 1970-01-01
        相关资源
        最近更新 更多