如何将输出从作为参数提供的管道重定向到变量

时间:2018-09-21 10:16:45

标签: perl

因此,我已经实现了捕获管道的功能

sub capture_stdout (&) {    
    my $s;
    open(local *STDOUT, '>', \$s);
    shift->();
    return $s;
}

sub capture_stderr (&) {
    my $s;
    open(local *STDERR, '>', \$s);
    shift->();
    return $s;
}

这些工作很棒。现在,我面临的挑战是,我想创建一个函数,该函数将管道作为参数,并将所有它们重定向到单个子目录中。 我尚未使它成功。到目前为止,我已经完成了一些编译工作;

sub capture(@&) {
    my $c = pop;
    my $o = [];
    say {$_[$_]} $_[$_] for (0 .. $#_);
    open(local *{$_[$_]}, '>', \$o->[$_]) for (0 .. $#_);
    $c->();
    return $o;
}

use Data::Dumper;
say Dumper( capture *STDOUT, *STDERR, sub{ say 1; warn 2; } );

,但不捕获任何内容。我似乎无法弄清楚如何解决它。不过,我确信local *{$_[$_]}需要修复,尽管我可能是错的。 完整的输出是:

*main::STDOUT
*main::STDERR
1
2 at capture.pl line 15.
$VAR1 = [
      undef,
      undef
    ];

那么问题就来了:甚至有可能做我正在尝试的事情,如果可以,怎么做?

谢谢。

3 个答案:

答案 0 :(得分:3)

您的代码存在的问题是local的效果在您的结尾处被撤消了

... for (0 .. $#_);

循环。在您调用$c->()时,文件句柄再次具有其原始值。

所以...

  • 您要本地化任意数量的变量。
  • 您不能使用块(例如for (...) { ... }),因为local在其作用域的末尾被撤消了。
  • 您不能使用后缀for,因为显然它会隐式创建自己的微型作用域。

解决方案? goto,当然!

(或者您可以使用递归:使用一个块,但不要离开它或循环返回。只需本地化一个变量,然后用剩余的变量调用自己即可。但是goto很有趣。)

sub capture {
    my $c = pop;
    my $o = [];

    my $i = 0;
    LOOP: goto LOOP_END if $i >= @_;
    local *{$_[$i++]};
    goto LOOP;
    LOOP_END:

    open(*{$_[$_]}, '>', \$o->[$_]) or die "$_[$_]: $!" for 0 .. $#_;
    $c->();
    return $o;
}

有效地,我们创建了一个循环,而没有输入/保留任何作用域。

答案 1 :(得分:1)

您实际上需要切换文件句柄。为此,首先保存现有的句柄。然后创建指向您的输出数据结构的新文件。运行代码后,还原原始句柄。

sub capture {
    my $c = pop;

    # we will keep the original handles in here to restore them later
    my @old_handles;

    my $o = [];
    foreach my $i (0 .. $#_) {

        # store the original handle
        push @old_handles, $_[$i];

        # create a new handle
        open my $fh, '>', \$o->[$i] or die $!;

        # stuff it into the handle slot of the typeglob associated with the old handle
        *{$_[$i]} = $fh;
    }

    # run callback
    $c->();

    # restore the old handles
    *{$_[$_]} = $old_handles[$_] for 0 .. $#_;

    return $o;
}

答案 2 :(得分:1)

解决方案:

最终产品,其复杂程度不及原始goto循环:

=pod

=item C<capture>

capture takes a list of pipes/filehandles, a code block or sub, optionally arguments to send to
said block and returns any captured output as a string, or an array of strings.

    my ($out, $err) = capture *STDOUT, *STDERR, sub { say 'faijas'; warn @_; }, 'jee';
    my $output = capture *STDOUT, sub { say 'jee'; };

=cut

sub capture(@&;@) {
    my (@o, @h);
    # walk through @_, grab all filehandles and the code block into @h
    push @h, shift while @_ && ref $h[$#h] ne 'CODE';
    my $c = pop @h; # then separate the code block from @h, leaving only handles

    # Really we want to do: open(local *{$_[$_]}, '>', \$o->[$_]) for (0 .. $#_);
    # but because of scoping issues with the local keyword, we have to loop without
    # creating an inner scope
    my $i = 0;
    R: open(local *{$h[$i]}, '>', \$o[$i]) or die "$h[$i]: $!" ;
    goto R if ++$i <= $#h;

    $c->(@_);
    return wantarray ? @o : $o[0];
}

非常感谢@melpomene和@simbabque帮助我解决了最初的问题,并感谢@ikegami指出了疏忽。