简体   繁体   English

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

[英]How to redirect output from pipe provided as argument to a variable

So i've made functions to capture pipes 所以我做了捕获管道的功能

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

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

These work great. 这些工作很棒。 Now the challenge i am facing is, i want to make a function that takes the pipe(s) as arguments, and redirects all of them in a single sub. 现在,我面临的挑战是,我想创建一个函数,该函数将管道作为参数,并将所有它们重定向到单个子目录中。 I have yet been unsuccessful in making it work. 我尚未使它成功。 So far I've cooked up something that compiles; 到目前为止,我已经完成了一些编译工作。

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; } );

but it does not capture anything. 但它没有捕获任何东西。 I cant seem to figure out how to fix it. 我似乎无法弄清楚如何解决它。 I'm convinced however that it is the local *{$_[$_]} that needs fixing, though i could be wrong. 但是,我确信需要修复的是local *{$_[$_]} ,尽管我可能是错的。 The complete output is: 完整的输出为:

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

So then the question: Is it even possible to do what I'm attempting, and if so, how? 那么问题就来了:甚至有可能做我正在尝试的事情,如果这样做,怎么做?

Thank you. 谢谢。

The problem with your code is that the effects of local are undone at the end of your 您的代码存在的问题是,在您的末尾, local的影响被撤消了

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

loop. 环。 By the time you call $c->() , the filehandles have their original values again. 到您调用$c->() ,文件句柄再次具有其原始值。

So ... 所以...

  • You want to localize an arbitrary number of variables. 您要本地化任意数量的变量。
  • You can't use blocks (eg for (...) { ... } ) because local is undone at the end of the scope it's in. 您不能使用块(例如, for (...) { ... } ),因为在其所在作用域的末尾会撤消local
  • You can't use postfix for because apparently it implicitly creates its own mini-scope. 您不能使用后缀for因为它显然隐式创建了自己的微型作用域。

The solution? 解决方案? goto , of course! goto当然!

(Or you could use recursion: Use a block, but never leave it or loop back. Just localize a single variable, then call yourself with the remaining variables. But goto is funnier.) (或者,您可以使用递归:使用一个块,但不要离开它或循环返回。只需本地化一个变量,然后用其余变量调用自己即可。但是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;
}

Effectively we've created a loop without entering/leaving any scopes. 实际上,我们创建了一个循环而没有输入/保留任何作用域。

You need to actually switch out the file handles. 您实际上需要切换文件句柄。 To do that, first save the existing handles. 为此,首先保存现有的句柄。 Then create new ones that point into your output data structure. 然后创建指向您的输出数据结构的新文件。 Once you've run the code, restore the original handles. 运行代码后,还原原始句柄。

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;
}

SOLUTION: 解:

The final product, not nearly as convoluted as the original goto loop: 最终产品的复杂程度不及原始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];
}

Big thanks to @melpomene and @simbabque for helping me around the initial problem, and @ikegami for pointing out oversights. 非常感谢@melpomene和@simbabque帮助我解决了最初的问题,并感谢@ikegami指出了疏忽。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM