Home > Software engineering >  Capture STDOUT to file with Capture::Tiny
Capture STDOUT to file with Capture::Tiny

Time:03-24

I am trying to capture STDOUT to a file with Capture::Tiny.

To give you the bigger picture, my end-goal is to:

  1. run a Blender process on the command line
  2. capture the log to a filehandle
  3. watch the filehandle "grow"
  4. pipe the content of the filehandle to a websocket for the user to watch progress via a web page

My plan is to use Capture::Tiny, and the example provided in this thread.

However I am stuck at step 1: I can capture STDOUT and STDERR as expected like this:

use IO::File;
use Capture::Tiny qw/capture/;

$\ = "\n"; $, = "\t";

my $blender = '/home/simone/blender/blender-3.1.0-linux-x64/blender -b /home/simone/some_file.blend --python /home/simone/blender_scripts/some_script.py';

my ($stdout, $stderr, $exit) = capture {
  system( $blender );
};

print join "\n", "STDOUT", $stdout =~ s/Time/Foobar/rg;
print '-' x 80;
print join "\n", "STDERR", $stderr;
print '-' x 80;

which produces the correct output but - as far as I can understand - does not let me watch it progress.

Doing this:

my $out_fh = IO::File->new("out_stdout.txt", "w ");
my $err_fh = IO::File->new("out_stderr.txt", "w ");

capture { qx/$blender/ } stdout => $out_fh, stderr => $err_fh;

leaves me with two empty files. Those are the two files I'd like to watch later.

How should I do this? is there a better (i.e.: workable) way?

CodePudding user response:

system (or qx) run the command to completion and then return. You need to run the command asynchronously. IPC::Run is my preferred solution for this type of interaction.

CodePudding user response:

capture { qx/$blender/ } stdout => $out_fh, stderr => $err_fh;

should be

capture { system($blender) } stdout => $out_fh, stderr => $err_fh;

qx// causes the output to be captured, overriding the redirection created by capture { }.

For example,

use strict;
use warnings;

use Capture::Tiny qw( capture );

open( my $out_fh, '>', 'out' ) or die $!;
open( my $err_fh, '>', 'err' ) or die $!;

my $cmd = <<'EOS';
perl -e'$| = 1; while ( 1 ) { CORE::say   $i; sleep 1; }'
EOS

my $e;
capture { system($cmd); $e = $?; } stdout => $out_fh, stderr => $err_fh;

die( "Killed by signal ".( $e & 0x7F )."\n" ) if $e & 0x7F;
die( "Exited with error ".( $e >> 8 )."\n" )  if $e >> 8;
$ perl a.pl &
[1] 19051

$ cat out
1
2
3

$ cat out
1
2
3
4
5

$ kill %1

But you talk of piping the information. If that's to be done by the Perl script, Capture::Tiny won't help. You can use IPC::Run.

use IPC::Run qw( run );

sub do_something_with_line {
   print $_[0] =~ s/Time/Foobar/rg;
}

my $cmd = [ "perl", "-e", '$|=1; while ( 1 ) { CORE::say "Time ",   $i; sleep 1; }' ];

my $buf = '';
run $cmd,
   '>', sub {
      $buf .= $_[0];
      while ( $buf =~ s/^(.*\n)// ) {
         do_something_with_line( $1 );
      }
   };

my $e = $?;

do_something_with_line( $buf ) if length( $buf );

die( "Killed by signal ".( $e & 0x7F )."\n" ) if $e & 0x7F;
die( "Exited with error ".( $e >> 8 )."\n" )  if $e >> 8;
$ perl b.pl
Foobar 1
Foobar 2
Foobar 3
Foobar 4
Foobar 5
^C

(Add '2>', ... to do something with stderr.)

  •  Tags:  
  • perl
  • Related