提问人:Jacob Wegelin 提问时间:3/16/2023 更新时间:5/5/2023 访问量:117
打开perl后,如何在不打印到磁盘的情况下捕获bash STDOUT和STDERR?
With perl open, how can I capture bash STDOUT & STDERR without printing to disk?
问:
下面的 perl 脚本使用
命令。find
open
STDOUT 进入 CMD 文件句柄并 STDERR(如果有)将进入磁盘上的临时文件。
如果用户在命令行上犯了错误,则将产生 STDERR。 但是,即使用户犯了错误,STDOUT 仍然可能包含有用的数据,就像我下面的示例一样。
因此,我们希望在 perl 脚本中有两个独立的变量,分别是 分别来自 unix 命令的 STDOUT 和 STDERR。
在当前脚本中,STDERR 只能通过首次打印获得 它复制到磁盘上的文件,然后读取该文件。
有没有办法在单独的变量中同时捕获 STDOUT 和 STDERR,而无需将 STDERR 打印到磁盘上的物理文件的中间步骤?它可以以某种方式直接打印到 perl 文件句柄或 perl 变量吗?
我知道可以在 perl 中使用而不是显式系统调用。但可以应用于其他 unix 命令,因此当前问题的答案可以应用于任意数量的 unix 命令。File::Find
open
> 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;
但是,如果您有兴趣了解所涉及的系统调用,请继续阅读。
- 创建三个管道。
- 如果此操作失败,
- 流产。
- 叉。
- 如果此操作失败,
- 流产。
- 在孩子身上,
- 关闭第一个管道的读取端。
- 关闭第二个管道的读取端。
- 关闭第三个管道的读取端。
- 设置第一个管道的 close-on-exec 标志。
- 打开。
/dev/null
- 如果此操作失败,
- 将错误消息写入第一个管道。
- 叫。(退出代码无关紧要。
_exit( 255 )
- 用于将手柄复制到 fd 0 上。
dup2
/dev/null
- 如果此操作失败,
- 将错误消息写入第一个管道。
- 叫。(退出代码无关紧要。
_exit( 255 )
- 将手柄关闭到 。
/dev/null
- 用于将第二个管道的写入端复制到 fd 1 上。
dup2
- 如果此操作失败,
- 将错误消息写入第一个管道。
- 叫。(退出代码无关紧要。
_exit( 255 )
- 关闭第二个管道的写入端。
- 用于将第二个管道的写入端复制到 fd 2 上。
dup2
- 如果此操作失败,
- 将错误消息写入第一个管道。
- 叫。(退出代码无关紧要。
_exit( 255 )
- 关闭第三个管道的写入端。
exec
程序。- 如果这失败了(即如果我们到达这里),
- 将错误消息写入第一个管道。
- 叫。(退出代码无关紧要。
_exit( 255 )
- 关闭第一个管道的写入端。
- 关闭第二个管道的写入端。
- 关闭第三个管道的写入端。
- 创建变量 。
$error_msg
- 圈
- 从第一个管道读取。
- 如果达到 EOF,
- 退出循环。
- 将已读的内容附加到 。
$error_msg
- 如果定义,
$error_msg
- 启动子项时出错。 包含原因。
$error_msg
- 流产。
- 启动子项时出错。 包含原因。
- 关闭第一个管道。
- 为第二个和第三个管道创建位域。
select
- 创建两个初始化为空字符串的变量。每个管道都将存储从剩余的两个管道之一接收的数据。
- 当位域仍设置位时,
- 用于等待数据。
select
- 对于每个现成的手柄,
- 用于从就绪手柄读取。
sysread
- 如果此操作失败,(即如果返回 -1),
sysread
- 这不应该发生。
- 清除位字段中的相应位。
- 如果 EOF(即如果返回零),
sysread
- 清除位字段中的相应位。
- 还
- 将读取的数据追加到相应的变量中。
- 用于从就绪手柄读取。
- 用于等待数据。
- 关闭第二根管道。
- 关闭第三根管道。
- 用于等待孩子退出。
waitpid
IPC::Open3 可以为您处理步骤 1 到 12。open3
IO::Select 可以为您简化步骤 13 到 15。
还有简单的 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 代码,而不仅仅是通过 .因此,无需进行 shell 重定向(可以从命令中删除它)。system
2> filename
这确实在内部使用文件,也可以显式管理(例如,这里详细讨论)。但我认为问题在于避免在代码中处理文件的需要。
请小心将用户输入作为参数传递给命令。
我建议你像池上建议的那样使用。我只是想回答OP问的这部分问题:IPC::Run
那么我该如何自己重定向呢?
用于复制文件描述符。请注意,这仍然会写入单独的文件,并且仅回答重定向部分。open
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'
评论
join
open
open
find: 2: unknown primary or operator $VAR1 = { 'command' => [ 'find', '-s', '.', '-maxdepth', '1', '2', '>', './z' ], <etc> }; $VAR1 = [];
2>