I have a piece of code that is formed through string processing and I am trying to run an eval to capture the output of the code into another string. The piece of code is formed some steps itself, which means it's not in body of Perl program in one piece. This is why I am using eval.
I tried using Tiny::Capture
without capture
, capture_merged
, capture_stdout
and also Safe
with Safe->new->reval()
.
use strict;
use warnings;
use Capture::Tiny 'capture';
use Capture::Tiny 'capture_stdout';
use Capture::Tiny 'capture_merged';
# $num_loops is defined inside main code body.
my $num_loops = 10;
#In reality, new code is formed through a series of steps.
my $new_code = "foreach \$idx (0..\$num_loops-1) {print \"I am at iteration number \$idx\n\";}";
my $out = capture_merged {eval $new_code;};
print $out;
I'm using Perl 5.30.
The piece of code above isn't printing anything out. Neither does it print any error in log. I have given intermediate prints in original code to make sure the string thrown into eval has real code, so that's not an issue.
What is the issue in the code above?
CodePudding user response:
Documentation is written for a reason, you should not assume return value from a function -- documentation gives details what is a return value.
Sample code how you could use eval
for your situation.
use strict;
use warnings;
my $num_loops = 10;
my $new_code = '
my $r;
$r .= "I am at iteration number $_\n" for 0..$num_loops-1;
return $r;
';
my $out = eval $new_code;
print $out;
Output
I am at iteration number 0
I am at iteration number 1
I am at iteration number 2
I am at iteration number 3
I am at iteration number 4
I am at iteration number 5
I am at iteration number 6
I am at iteration number 7
I am at iteration number 8
I am at iteration number 9
CodePudding user response:
If I understand you correctly, you want to do the same as the
$var = `... commands ...`;
$var = qx(.. commands ..);
but with commands
being perl instead of shell code, and without forking a separate process.
You can do that by localizing STDOUT
and re-opening the local filehandle as an in-memory file.
Simple example, without any external module:
use strict;
sub qp(&){
my $s;
open local *STDOUT, '>', \$s or die "open in-memory file: $!";
&{$_[0]};
die $@ if $@;
$s;
}
# Usage:
my $a = qp { eval 'print "in"' };
print "<$a>\n";
print "out\n";
# 'in' will go into the string, 'out' to the actual stdout
# Use with your example (fixed to use 'my $idx'):
my $num_loops = 10;
my $new_code = "foreach my \$idx (0..\$num_loops-1) {print \"I am at iteration number \$idx\n\";}";
my $out = qp { eval $new_code };
print $out;
The limitations of this are
- that will not catch
syswrite
output -- that could be easily fixed bytie
ing the localizedSTDOUT
and implementingPRINT
,PRINTF
andWRITE
to append to a string. - that will not catch the output from separate processes spawn by perl -- that could only be fixed by re-opening
STDOUT
to a temporary file (that's whatCapture::Tiny
does under the hood).
Why your example doesn't work
In your eval'd code, you're using foreach $idx (...)
without $idx
being defined, and since you're within the scope of use strict
, that errors out, and your code is not run. But you're not checking the value of $@
, and you're not able to see that error. Change it to foreach my $idx (...)
as I did above.