Polo
Polo

Reputation: 129

Print output from an external command and save to a variable in perl

In perl, how do you run an external process showing output as it happens, and save that output to a variable?

I'd like to use IPC::Run3, but it's not required.

Eg, this won't work because it doesn't take multiple handles:

use IPC::Run3;
my $out;
my @cmd = qw!/usr/bin/tail -f /tmp/writetothis!;
run3( \@cmd, undef, [ <STDOUT>, \$out ] );

Upvotes: 1

Views: 278

Answers (2)

ikegami
ikegami

Reputation: 386706

From the documentation, it would seem you could use the following:

use IPC::Run3 qw( run3 );

my $out = '';
run3 \@cmd,
   \undef,
   sub {
      $out .= $_[0];
      print $_[0];
   };

But the callback is only called once the child completes, even if you disable buffering in the child. (Full demonstration below.) That means you might as well have used the following:

use IPC::Run3 qw( run3 );

my $out = '';
run3 \@cmd, \undef, \$out;
print $out;

If that's a problem, then you could use the virtually identical but more powerful IPC::Run. (Full demonstration below.)

use IPC::Run qw( run );

my $out = '';
run \@cmd,
   \undef,
   sub {
      $out .= $_[0];
      print $_[0];
   };

This is still slave to any buffering performed by the child. If that's a problem, you could use >pty> to try to convince the child to avoid buffering. (Full demonstration below.)

use IPC::Run qw( run );

my $out = '';
run \@cmd,
   \undef,
   '>pty>' => sub {
      $out .= $_[0];
      print $_[0];
   };

Full demonstration of IPC::Run3 solution:

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

use IPC::Run3 qw( run3 );

my @cmd = ( $^X => ( -e => <<'__EOS__' ) );
   use Time::HiRes qw( sleep );

   $| = 1;

   for (1..10) {
      print "$_\n";
      sleep(0.2);
   }
__EOS__

my $out = '';
run3 \@cmd,
   \undef,
   sub {
      $out .= $_[0];
      print $_[0];
   };

say "[$out]";

Full demonstration of IPC::Run solution:

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

use IPC::Run qw( run );

my @cmd = ( $^X => ( -e => <<'__EOS__' ) );
   use Time::HiRes qw( sleep );

   $| = 1;

   for (1..10) {
      print "$_\n";
      sleep(0.2);
   }
__EOS__

my $out = '';
run \@cmd,
   \undef,
   sub {
      $out .= $_[0];
      print $_[0];
   };

say "[$out]";

Full demonstration of IPC::Run solution with >pty>:

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

use IPC::Run qw( run );

my @cmd = ( $^X => ( -e => <<'__EOS__' ) );
   use Time::HiRes qw( sleep );

   for (1..10) {
      print "$_\n";
      sleep(0.2);
   }
__EOS__

my $out = '';
run \@cmd,
   \undef,
   '>pty>' => sub {
      $out .= $_[0];
      print $_[0];
   };

say "[$out]";

Upvotes: 1

choroba
choroba

Reputation: 242383

You can achieve that without any module using the "read from pipe" mode of open:

#!/usr/bin/perl
use strict;
use warnings;

open my $in, '-|', qw{ /usr/bin/tail -f /tmp/writetothis };

my $out;
while (<$in>) {
    $out .= $_;
    print;
    last if /^$/;  # Empty line to end the loop.
}
print $out;

Upvotes: 2

Related Questions