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

Håk*_*and 5 perl

我想捕获一个命令的所有输出(两者STDOUTSTDERR),这些命令也需要来自终端窗口的用户交互,即它读取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!";
Run Code Online (Sandbox Code Playgroud)

我试过用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;
}
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

user.py:

#! /usr/bin/env python3

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

red*_*neb 3

TL/DR有一个解决方案,它有点难看,但它有效。有一些小警告。

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

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

#!/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
Run Code Online (Sandbox Code Playgroud)

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

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

最后,这在 Windows 上不起作用。