Combine redirecting open() to scalar with system() - perl

I wrote a routine to "safely" execute some command, and I wanted to capture STDOUT and STDERR in string variables using open(STDOUT, '+<', \$stdout) and similar for STDERR.
I verified via print "Test\n" and print STDERR "Test2\n" that the redirection works inside the routine (I can find the outputs in $stdout and $stderr).
However when I run the command through system() (Perl's version), the output still goes to the terminal.
So I wonder: Is opening a scalar value available for Perl's own I/O only?
And if so, how would I capture the STDOUT and STDERR from the system() call without using temporary files (having their own issues)?
(I've seen https://stackoverflow.com/a/109672/6607497 already)
The preferred solution (if such exists) should use as few extra packages as possible, and it should run with SLES 12 or SLES 15 (openSUSE Leap 15.2).
Those distributions only offer a limited set of Perl modules.

You can easily do this using IPC::Run to capture output.
Test script that writes to standard output and error:
#!/bin/sh
# demo.sh
echo "To Standard Output"
echo "To Standard Error" >&2
and perl script that runs it:
#!/usr/bin/env perl
use warnings;
use strict;
use IPC::Run qw/run/;
my ($out, $err);
run ["sh", "demo.sh"], \undef, \$out, \$err;
print "Standard output: ", $out;
print "Standard error: ", $err;
gives the following output:
$ perl demo.pl
Standard output: To Standard Output
Standard error: To Standard Error
Alternative using IPC::Run3 (Which might be more desirable if you don't need any of IPC::Run's more advanced features):
#!/usr/bin/env perl
use warnings;
use strict;
use IPC::Run3;
my ($out, $err);
run3 ["sh", "demo.sh"], \undef, \$out, \$err;
print "Standard output: ", $out;
print "Standard error: ", $err;

Related

Unable to read IPC::Open3 child's output

This code is used to read and write through the pipe, but it seems it is not working well.
use strict;
use IPC::Open2;
my $st1="String1\n";
my $st2="String2\n";
my $st3="String3\n";
my $st4="String4\n";
my $st5="String5\n";
my $joint=$st1.$st2.$st3.$st4.$st5;
my $r;
my $pid = open2(\*CHILD_IN, \*CHILD_OUT, 'java -Dfile.encoding=UTF8 -cp abc.jar:xxx.jar TestCode')
or die "open2() failed $!";
print CHILD_IN $joint;
$r=<CHILD_OUT>;
print "Got $r from child\n";
print "[OUTPUT]: $_" while (<CHILD_OUT>);
This code is only reading first line of the output that too which is stored in $r. Not going inside while loop. Although there is a lot of output by executing the command.
You have a typo.
$r=<CHLD_OUT>;
and
print "[OUTPUT]: $_" while (<CHILD_OUT>);
It's CHLD_OUT versus CHILD_OUT.
It's probably better to always use
use warnings
It'd save you from the trouble, by showing a warning message:
readline() on unopened filehandle CHILD_OUT at x.pl line 19.

open(HANDLE, "-|", $cmd, #args) : how to read stderr?

I want to read the output of a command including the stderr into HANDLE:
open(HANDLE, "-|", $cmd, #args);
But the above command just reads the stdin?
How can I also read the stderr?
The IPC::Run module provides a run function that works like a supercharged system. It allows us to collect the output of STDERR and STDOUT combined:
run [$cmd, #args], '&>', \my $output;
after that, the $output variable holds the combined output as a string.
Example:
use IPC::Run qw/ run /;
run ['perl', '-E say "stdout"; say STDERR "stderr"'], '&>', \my $output;
print uc $output;
Output:
STDOUT
STDERR
I don't know how to use a filehandle in place of the scalar reference so that the output can be read normally in a while(<$fh>) loop.
You're going to want to look at IPC::Open3 which starts a process and provide separate file handles for writing to the child, and reading the child's STDOUT and STDERR.
I use Bash redirection as below in my perl code:
open (CMDOUT, "df -h 2>&1 |");

How to capture both STDOUT and STDERR in two different variables using Backticks in Perl

Let's say I want to run an external program from my script with backticks and at the same time I want to capture both STDOUT and STDERR but in two different variables. How can I do that? For istance if I run this script...
my $cmd = `snmpwalk -v $version -c $community $hostname $oid`;
...if there is no error everything works just fine BUT if the command raise an error this error will be printed on the command line and I don't want that to happen. I want to capture the error as well. Nothing has to be printed on the screen. Any ideas?
You needn't go all the way to open3, which IIRC is only for when you need to read and write to an external command, and even then there are other methods.
For your problem I suggest using Capture::Tiny, which can capture (or even tee) the STDOUT and STDERR from anything run inside its block. For example, per your question:
#!/usr/bin/env perl
use strict;
use warnings;
use Capture::Tiny qw/capture/;
...
my ($stdout, $stderr) = capture {
system ( "snmpwalk -v $version -c $community $hostname $oid" );
};
For another example consider this functioning code:
#!/usr/bin/env perl
use strict;
use warnings;
use Capture::Tiny qw/capture/;
my ($stdout, $stderr) = capture {
system ( "echo 'hello'" );
system ( "date" );
warn "Arg1!";
};
print "STDOUT:\n$stdout";
print "STDERR:\n$stderr";
which just gave me:
STDOUT:
hello
Mon Dec 19 23:59:06 CST 2011
STDERR:
Arg1! at ./test.pl line 11.
The only way to do this with backticks is to redirect to a file inside the shell command:
my $cmd = `snmpwalk -v $version -c $community $hostname $oid 2>error.dat`;
If you want to capture the STDERR inside your script, you need IPC::Open3 instead of backticks
In the Perl FAQ you have different options depending how do you want to proceed:
http://perldoc.perl.org/perlfaq8.html#How-can-I-capture-STDERR-from-an-external-command%3f
IO::CaptureOutput
is a very convenient wrapper for what you want to do.

command output is directed to the screen

novice Perl programmer here!.
I'm using the system() function to get the return code for an external program (in this case - php), however, the command's output is still printed to the screen.
How do I prevent it from doing so?
My code is:
use strict; use warnings;
print 'Return code:', system('php -l wrong.php'), "\n";
This code does print the return code, but it also print the the output of the command executed.
Any help will be appreciated!
EDIT: further testing shown that this happens while only using the php lint command.. using it with other commands doesnt print anything...
What you want is IPC::Open3:
use IPC::Open3;
use Symbol qw(gensym);
my $err = gensym; #work around IPC::Open3's interface
my $pid = open3 my $wtr, my $rdr, $err,
'some cmd', 'arg1', 'arg2', ...;
The separates out stdin into $wtr, stdout into $rdr, and stderr into $err
This also gives you maximum control over communicating with the process
If you are on a UNIX-like OS, you should be able to redirect the output in the command:
Try:
system('php -l wrong.php >> /dev/null') to get rid of what's being sent to stdout.
You could also open the command with a pipe to handle the output directly, which should be more portable.

How can I append the file foo2.txt to foo1.txt?

Is there any method to execute foo2.pl from foo1.pl in Perl, and append the foo2.txt to the foo1.txt then create foo3.txt? thanks.
i.e.
foo1.pl
print "Hello"; # output to foo1.txt;
foo2.pl
print "World"; # output to foo2.txt;
How to create foo3.txt file based on foo1.pl.
foo3.txt
Hello
World
Something like append foo2.txt to foo1.txt.
As i know, I can open foo1.txt and foo2.txt, then include the lines in foo3.pl.
print FOO3_TXT (<FOO1_TXT>);
print FOO3_TXT (<FOO2_TXT>);
Is there any good method?
Update my test (ActivePerl 5.10.1)
My foo.pl
#!C:\Perl\bin\perl.exe
use strict;
use warnings;
print "world\n";
my hw.pl (foo.pl and hw.pl at the same directory)
#!C:\Perl\bin\perl.exe
use strict;
use warnings;
print 'hello ';
print `./foo.pl`;
Output
**D:\learning\perl>hw.pl
hello '.' is not recognized as an internal or external command,
operable program or batch file.**
If hw.pl updated {}:
#!C:\Perl\bin\perl.exe
use strict;
use warnings;
print q{hello }, qx{./foo.pl};
Now Output. (a little different for the loacation of hello)
D:\learning\perl>hw.pl
'.' is not recognized as an internal or external command,
operable program or batch file.
hello
[Update].
Fixed. see answer,
run this as a script
perl foo1.pl > foo3.txt;
perl foo2.pl >> foo3.txt;
contents of foo1.pl
!#/bin/perl
print "Hello";
contents of foo2.pl
!#/bin/perl
print "World";
or
simply use the cat command if you are running linux to append foo2.txt to foo1.txt.
Just in case you are being literal about execute foo2.pl from foo1.pl in Perl then this is what you can do:
print 'hello ';
print qx(perl foo2.pl);
qx is another way to run system commands like backticks. Thus perl foo2.pl is run with the output being sent back to your calling perl script.
So here the same using backticks. Also it uses a direct call to script (which is better):
print 'hello ';
print `./foo2.pl`;
And if you are expecting lots of output from the script then its best not to load it all into memory like above two examples. Instead use open like so:
print 'hello ';
open my $foo2, '-|', './foo2.pl';
print <$foo2>;
close $foo2;
And you can wrap this up into one print statement for "hello world" example:
print 'hello ', do {
open my $foo2, '-|', './foo2.pl';
<$foo2>;
};
/I3az/
Using a shell script (for example, a .bat file on Windows) to run various Perl scripts and combine their output is one way to solve the problem. However, I usually find that Perl itself provides a more powerful and flexible environment than shell scripts. To use Perl in this way, one place to start is by learning about the system and exec commands.
For example:
# In hello.pl
print "Hello\n";
# In world.pl
print "World\n";
# In hello_world.pl.
use strict;
use warnings;
system 'perl hello.pl > hello_world.txt';
system 'perl world.pl >> hello_world.txt';
You can use the following code also
file1.pl
use strict;
use warnings;
open (FH,">file") or die "$! can't open";
print FH "WELCOME\n";
file2.pl
use strict;
use warnings;
open (FH,">>file") or die "$! can't open";
print FH "WELCOME2\n";
The file content is
WELCOME
WELCOME2
If you know beforhand that the script you want to execute from inside the other script is also Perl, you should use do EXPR (https://perldoc.perl.org/functions/do.html).
This executes the contents of the file EXPR in the context of the running perl process and saves you from starting new cmd.exe and perl.exe instances.
hello.pl:
print "Hello";
do "world.pl";
wordl.pl:
print "World";