因此,我已经实现了捕获管道的功能
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
];
那么问题就来了:甚至有可能做我正在尝试的事情,如果可以,怎么做?
谢谢。
答案 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指出了疏忽。