$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

perlでremoteのサーバでコマンド実行し、STDOUTとSTDERRをキャプチャする

perlでremoteのサーバにコマンドを実行したい時があります。こういう時はNet::OpenSSHが便利です。
Net::OpenSSHでコマンド実行するためには、以下を実行します。結果は手元のSTDOUTやSTDERRに出力されます。

use Net::OpenSSH;
my $ssh = Net::OpenSSH->new('hogehoge.hostname', user => 'hoge');
$ssh->system('ls -x ~/');

さらにこの時のSTDOUTとSTDERRを変数に入れたい時があります。その場合は以下のようにします。

use Net::OpenSSH;
my $ssh = Net::OpenSSH->new('hogehoge.hostname', user => 'hoge');
my ($stdout, $stderr) = $ssh->capture2('ls -x ~/');
warn $stdout; # hoge  fuga  piyo

($stdout, $stderr) = $ssh->capture2('cat ~/');
warn $stderr; # cat: /home/app/: Is a directory

しかしcapture2では手元のSTDOUTやSTDERRに何も出力されなくなってしまうため、コマンド実行が終わるまで固まっているような状態になります。例えばperlbrew install perl-5.14.2とかしたらひどいことになります。
そこでリアルタイムで手元のSTDOUTやSTDERRに結果を出力しながら、変数にも入れたい時はCapture::Tinyを使って、以下のようにします。

use Capture::Tiny ':all';
use Net::OpenSSH;

my ($stdout, $stderr, $result) = tee sub {
    $ssh->system('ls -x ~/');
};
warn $stdout; # hoge  fuga  piyo

本当はNet::OpenSSHだけで完結させたいのですが、見る限りそういうふうなメソッドもなさそうだったのでこんな感じになりました。ちょっとバッドノウハウ感ありますね。