下面的 perl 脚本调用 unix
find
使用
open
命令。
STDOUT 进入 CMD 文件句柄和 STDERR(如果有)进入磁盘上的临时文件。
STDERR 如果用户在命令行上犯了错误,将导致。 但即使用户犯了错误,STDOUT 仍然可能包含有用的数据,如下面的示例所示。
因此我们需要在 perl 脚本中有两个单独的变量,分别保存 分别来自 unix 命令的 STDOUT 和 STDERR。
当前脚本中,STDERR只能通过先打印获得 它到磁盘上的文件并随后读取文件。
有没有一种方法可以在单独的变量中捕获 STDOUT 和 STDERR,而无需将 STDERR 打印到磁盘上的物理文件的中间步骤?它能以某种方式直接打印到 perl 文件句柄或 perl 变量吗?
我知道可以在 perl 中使用
File::Find
而不是显式系统调用。但是 open
可以应用于其他 unix 命令,因此当前问题的答案可以应用于任意数量的 unix 命令。
> cat z.pl
#!/usr/bin/env perl
use strict; use warnings;
use Data::Dumper qw(Dumper); $Data::Dumper::Sortkeys = 1;
my$work={};
$work->{tmpfile}='./z';
$work->{command}=join(' ',
'find -s ',
@ARGV,
'2>',
$work->{tmpfile},
);
open CMD, '-|', $work->{command} or die $work->{command},' cannot open';
$work->{return}{open}=$?;
my@result;
while(<CMD>)
{
chomp;
push@result,$_;
}
close CMD;
$work->{return}{close}=$?;
if(-s $work->{tmpfile})
{
open IN,'<',$work->{tmpfile} or die 'cannot read ',$work->{tmpfile},;
local$/=undef;
$work->{stderr}=<IN>;
close IN;
}
print Dumper $work;
print Dumper \@result;
> ./z.pl . piggy
$VAR1 = {
'command' => 'find -s . piggy 2> ./z',
'return' => {
'close' => 256,
'open' => 0
},
'stderr' => 'find: piggy: No such file or directory
',
'tmpfile' => './z'
};
$VAR1 = [
'.',
'./.z.pl.swp',
'./body',
'./body~',
'./snap',
'./title',
'./z',
'./z.pl',
'./z.pl~'
];
>
IPC::Run 让这一切变得简单。
use IPC::Run qw( run );
my @cmd = ( "find", "-s", ".", "piggy" );
run \@cmd, ">", \my $stdout, "2>", \my $stderr;
my $status = $?;
孩子继承了上面程序中的STDIN。如果不需要,您可以使用以下内容:
run \@cmd, "/dev/null", \my $stdout, \my $stderr;
但是如果您有兴趣了解所涉及的系统调用,请继续阅读。
/dev/null
._exit( 255 )
。 (退出代码无关紧要。)dup2
将句柄复制到/dev/null
到fd 0._exit( 255 )
。 (退出代码无关紧要。)/dev/null
.dup2
将第二个管道的写入端复制到fd 1._exit( 255 )
。 (退出代码无关紧要。)dup2
将第二个管道的写入端复制到fd 2._exit( 255 )
。 (退出代码无关紧要。)exec
程序。_exit( 255 )
。 (退出代码无关紧要。)$error_msg
.$error_msg
.$error_msg
,
$error_msg
包含原因。select
位域。select
等待数据。sysread
从准备好的手柄读取。sysread
返回 -1),
sysread
返回零),
waitpid
等待孩子退出IPC::Open3 的
open3
可以为您处理步骤 1 到 12。
IO::Select 可以为您简化步骤 13 到 15。
我建议你像
ikegami建议的那样使用
IPC::Run
。我只想回答这个:
那我怎么自己重定向它
sub run {
my $output = shift; # a file to redirect STDERR to
print "Running: ('" . join("', '", @_) . "') 2> $output\n";
# dup STDERR so that we can restore it later
open(my $err, '>&', \*STDERR) or die "Can't dup stderr: $!";
# reopen STDERR to output to the supplied file
open(STDERR, '>', $output) or die "Can't redirect stderr: $!";
# "open" your command
open(my $cmd, '-|', @_) or die "Can't exec $_[0]: $!";
while(<$cmd>) {
print $_;
}
close($cmd);
# restore STDERR
open(STDERR, '>&', $err) or die "Can't restore stderr: $!";
close($err);
}
my @cmd = ('find', '-s', '.', '-maxdepth', '1', '2');
run './z', @cmd;
STDOUT
上的输出:
Running: ('find', '-s', '.', '-maxdepth', '1', '2') 2> ./z
我的
find
不知道 -s
所以在 STDERR
上的输出(因此 ./z
的内容)变成了:
find: unknown predicate `-s'
还有简单的Capture::Tiny
use warnings;
use strict;
use feature 'say';
use Capture::Tiny qw(capture);
#my @cmd = ('find', '-s', @ARGV);
my @cmd = ( 'ls', '-l', (@ARGV ? shift : '.') );
my ($stdout, $stderr, $exit) = capture {
system( @cmd );
};
say $stdout if $stdout;
say "Error: $stderr" if $stderr;
say "exit: $exit" if $exit;
一个人可以在块内运行任何 Perl 代码,而不仅仅是通过
system
. 的外部命令
请小心将用户输入作为参数传递给命令。