加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

perl – 如何捕获命令的所有输出,这也需要终端的用户输入?

发布时间:2020-12-15 21:44:33 所属栏目:大数据 来源:网络整理
导读:我想捕获一个命令的所有输出(STDOUT和STDERR),该命令也需要来自终端窗口的用户交互,即它读取STDIN然后将某些内容打印到STDOUT. 这是我想要捕获输出的脚本的最小版本: user.pl: #! /usr/bin/env perluse feature qw(say);use strict;use warnings;print "En
我想捕获一个命令的所有输出(STDOUT和STDERR),该命令也需要来自终端窗口的用户交互,即它读取STDIN然后将某些内容打印到STDOUT.

这是我想要捕获输出的脚本的最小版本:

user.pl:

#! /usr/bin/env perl

use feature qw(say);
use strict;
use warnings;

print "Enter URL: ";
my $ans = <STDIN>;
# do something based on $ans
say "Verification code: AIwquj2VVkwlWEBwway";
say "Access Token: bskjZO8iZotv!";

我尝试使用Capture::Tiny

p.pl:

#! /usr/bin/env perl

use feature qw(say);
use strict;
use warnings;

use Capture::Tiny qw(tee_merged);

my $output = tee_merged {
    #STDOUT->autoflush(1);  # This does not work
    system "user.pl";
};

if ( $output =~ /Access Token: (.*)$/ ) {
    say $1;
}

但它不起作用,因为直到用户在终端输入输入后才显示提示.

编辑:

如果我用python脚本替换user.pl,它似乎工作正常.例如:

user.py:

#! /usr/bin/env python3

ans = input( 'Enter URL: ' )
# do something based on $ans
print( 'Verification code: AIwquj2VVkwlWEBwway' )
print( 'Access Token: bskjZO8iZotv!' )

解决方法

TL / DR有一个解决方案,它有点难看,但它的工作原理.有一些小问题.

这是怎么回事?问题实际上在user.pl中.您提供的示例user.pl的工作方式如下:首先将字符串Enter URL:打印到其标准输出,然后刷新其标准输出,然后从标准输入读取一行. stdout的刷新是由perl自动发生的:当你尝试从stdin读取< ..> (又名readline),perl刷新标准输出.正是这样才能使这样的程序正常运行.不幸的是,当stdout是tty(伪终端)时,perl似乎只实现了这种行为.如果没有,它在从stdin读取之前不会刷新stdout.这就是当您在交互式终端会话中执行脚本时脚本工作的原因,并且当您尝试捕获其输出时它无法正常工作(因为在这种情况下,它的stdout连接到管道).

如何解决这个问题?由于user.pl在stdout不是tty时行为不正常,我们必须使用tty. AFAIK,IPC::Run是唯一可以使用tty而不是普通管道捕获子进程输出的perl模块.不幸的是,当使用tty时,IPC :: Run不允许我们仅重定向stdout,它迫使我们重定向stdin.因此,我们必须代表子进程处理父进程中的stdin读取(yikes!).这是使用IPC :: Run的p.pl的示例实现:

#!/usr/bin/perl
use strict;
use warnings;
use IO::Handle;
use IPC::Run;

my $complete_output='';
my $in='';
my $out='';
my $h=IPC::Run::start ['./user.pl'],'<pty<',$in,'>pty>',$out;
while ($h->pumpable) {
    $h->pump;
    print $out;
    STDOUT->flush;
    if ($out eq 'Enter URL: ') {
        $in.=<STDIN>;
    }
    $complete_output.=$out;
    $out='';
}
$h->finish;
# do something with $complete_output here

所以这有点难看.例如,我们尝试检测子进程何时等待用户输入(通过查找字符串输入URL :),当它发生时,我们读取父进程中的用户输入,然后将其传递给子进程.另请注意,我们必须自己实现tee功能,因为IPC :: Run不提供.

有一些警告.我们处理用户输入的方式,如果子进程使用类似readline库的东西来支持行编辑,这将不起作用,因为我们使用简单的< STDIN>进行父进程中的所有读取.此外,因为在幕后使用tty而不是管道,所有用户输入都将回显到stdout.因此无论用户在提示符中输入什么内容,我们都将其放入$in中以将其发送到进程并从进程中返回(通过$out变量).但由于我们的终端也有回音,文本会出现两次.一种解决方案是过滤$out以删除用户输入并阻止我们打印它.

最后,这不适用于Windows.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读