2016-09-27 3 views
4

터미널 창과의 사용자 상호 작용이 필요한 명령의 출력 (모두 STDOUTSTDERR)을 캡처하고자합니다. 즉, STDIN을 읽고 STDOUT에 무엇인가를 인쇄합니다. 내가 Capture::Tiny를 사용하여 시도터미널에서 사용자 입력이 필요한 명령의 모든 출력을 캡처하는 방법은 무엇입니까?

#! /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!"; 

:

p.pl

user.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; 
} 

하지만 사용자가 터미널에 입력 한 후에야 프롬프트가 표시되기 때문에 작동하지 않습니다.

편집는 :

내가 파이썬 스크립트 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!') 
+1

'system "unbuffer user.pl";'(아마도 부모에서'$ | = 1;') – ikegami

+0

@ikegami 제안 해 주셔서 감사 합니다만 불행히도'system "unbuffer user.pl을 사용하면"some " 다른 문제. 프롬프트를 올바르게 표시하지만 입력을 입력하고 Enter 키를 누르면 중단되며 CTRL-C를 사용하여 종료해야합니다. ('sudo apt-get install expect'로'unbuffer' 명령을 먼저 설치해야만했습니다.) –

답변

2

TL/DR은 다소 추한, 해결책이있다, 그러나 그것은 작동합니다. 몇 가지 사소한 경고가 있습니다.

무슨 일 이니? 문제는 실제로 user.pl에 있습니다. 제공 한 샘플 user.pl은 다음과 같이 작동합니다. 문자열 Enter URL:stdout에 인쇄하기 시작한 다음 stdout을 플러시 한 다음 stdin에서 한 줄을 읽습니다. stdout의 플러시는 perl에 의해 자동으로 수행됩니다. stdin에서 <..> (일명 readline)으로 읽으려고하면 Perl은 stdout을 비 웁니다. 그것은 정확하게 이런 프로그램을 올바르게 작동하게합니다. 불행하게도, perl은 stdout이 tty (가상 터미널) 일 때만이 동작을 구현하는 것으로 보입니다. 그렇지 않다면 stdout을 플러시하지 않고 stdin에서 읽습니다. 따라서 대화 형 터미널 세션에서 스크립트를 실행할 때 스크립트가 작동하며 출력을 캡처하려고하면 제대로 작동하지 않습니다 (이 경우 stdout이 파이프에 연결되어 있기 때문입니다).

해결 방법?stdout이 tty가 아닌 경우 user.pl 오작동이므로 tty를 사용해야합니다. AFAIK, IPC::Run은 일반 파이프 대신 tty를 사용하여 하위 프로세스의 출력을 캡처 할 수있는 유일한 perl 모듈입니다. 불행히도 tty를 사용할 때 IPC::Runstdout 만 리디렉션 할 수 없기 때문에 stdin도 리디렉션하도록합니다. 따라서 자식 프로세스 (yikes!)를 대신하여 부모 프로세스에서 stdin의 읽기를 처리해야합니다.여기 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 

그래서이 다소 추한입니다. 예를 들어 하위 프로세스가 사용자 입력을 기다리고있을 때 (문자열 Enter URL:을 찾아서)이를 탐지하려고 할 때 부모 프로세스에서 사용자 입력을 읽은 다음 하위 프로세스에 전달합니다. 또한 우리는 IPC::Run이 제공하지 않기 때문에 스스로 티 기능을 구현해야한다는 것을 알아 두십시오.

몇 가지주의 사항이 있습니다. 우리가 사용자 입력을 처리하는 방식으로 서브 프로세스가 행 편집을 지원하기 위해 readline 라이브러리와 같은 것을 사용하는 경우, 부모 프로세스에서 모든 읽기를 단순한 <STDIN>으로 수행하기 때문에 이것이 작동하지 않습니다. 또한 파이프 대신 장면 뒤에 tty가 사용되므로 모든 사용자 입력이 stdout에 에코됩니다. 따라서 사용자가 프롬프트에 입력하는 모든 내용을 $in에 입력하여 프로세스로 보내면 ($out 변수를 통해) 프로세스에서 반환됩니다. 그러나 터미널이 에코를 가지고 있기 때문에 텍스트가 두 번 나타납니다. 한 가지 해결책은 필터 $out으로 사용자 입력을 제거하고 인쇄하지 못하도록하는 것입니다.

마지막으로 Windows에서는 작동하지 않습니다.

+0

@ikegami 제 대답은 OP가 원하는 것과 정확히 같습니다. OP는 사용자가 자식 프로세스와 상호 작용할 수 있도록하면서 자식 출력을 캡처하려고합니다. 제가 설명 하듯이 문제는 우리가 tty를 사용하기 때문에 아이를 대신하여'stdin'을 읽는 것을 처리해야한다는 것입니다. 제 대답을주의 깊게 읽으십시오 ('해결 방법'부분). 나는 명확히하기 위해 문장을 추가했다. 네가 나를 믿지 않는다면 해보 라. – redneb

+0

좋은 답변 주셔서 감사합니다! IPC :: Run의 기능에 익숙하지 않았습니다. 나는 무엇이 일어나고 있는지 이해하기 위해 더 자세히 연구해야 할 것입니다. –

0

는 귀하의 의견은 청각 장애에 직접하라는 메시지 작성합니다.

open TTY, '>', '/dev/tty'; # or 'con' in Windows 
print TTY "Enter URL:"; 
my $ans = <STDIN>; 
... 
+0

좋은 해결책 이었지만 일반적으로'user.pl' 스크립트는 수정할 수 없습니다. 블랙 박스로 받아 들여야합니다. –

관련 문제