1

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.

U. Windl
  • 3,480
  • 26
  • 54
  • 1
    https://metacpan.org/pod/Capture::Tiny::Extended can do that. – simbabque Mar 03 '21 at 09:20
  • I think `IPC::Run` can do that too. – Shawn Mar 03 '21 at 10:12
  • Try `perldoc IPC::Open3 ` – k-mx Mar 04 '21 at 12:31
  • @k-mx Did you try `IPC::Open3`? I mean: My code also redirects `STDOUT` and `STDERR`, but the code that writes to a scalar isn't "passed down" to the executed process. So I'm afraid the same will happen when using `IPC::Open3`... – U. Windl Mar 05 '21 at 06:41
  • @Shawn It seems `IPC::Run` cannot separate `STDOUT` and ´STDERR`. – U. Windl Mar 05 '21 at 06:45
  • @simbabque `Capture::Tiny` also cannot redirect `STDOUT` and `STDERR` to a scalar; the doc says: "*If STDOUT or STDERR are reopened to scalar filehandles prior to the call to capture or tee, then Capture::Tiny will override the output filehandle for the duration of the capture or tee call and then, for tee, send captured output to the output filehandle after the capture is complete.*" So it's basically redirecting to temporary files, the appending their contents to `STDOUT` and `STDERR` after the sub-process exited. – U. Windl Mar 05 '21 at 06:51
  • You are reading the documentation of Capture::Tiny. I pointed you to Capture::Tiny::Extended, which is a different module. – simbabque Mar 05 '21 at 11:30
  • @simbabque To quote from the docs of `Capture::Tiny::Extended`: "*Capture::Tiny::Extended is a fork of Capture::Tiny. It is functionally identical with the parent module, except for the differences documented in this POD.*" – U. Windl Mar 05 '21 at 13:47
  • I suppose the answer then is, there doesn't seem to be a way to do what you want. – simbabque Mar 05 '21 at 14:20
  • Re "*It seems IPC::Run cannot separate `STDOUT` and `STDERR`*", Untrue – ikegami Mar 09 '21 at 06:04
  • @ikegami Right, I did not read the the manual correctly it seems. – U. Windl Mar 09 '21 at 07:04

1 Answers1

2

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;
Shawn
  • 47,241
  • 3
  • 26
  • 60
  • I tried `IPC::Run3` executing `('lsof', '-p', $$)`. It clearly shows that it's using (deleted) temporary files as well. So actually all those "magic wrappers" around the problem just redirect to and from files transparently. – U. Windl Mar 08 '21 at 07:26
  • @U. Windl, IPC::Run uses pipes. Re "*just redirect to and from files transparently*", Well yeah. The child process can't write to a Perl scalar in another process for three reasons: 1) It has no idea what a Perl scalar is, 2) It would have no way to synchronize access to the scalar if it did know what a scalar is, and 3) it can't write to another process's memory space. Probably should have mentioned that one first :) – ikegami Mar 09 '21 at 06:01
  • @ikegami "*named* pipes" I guess. Anyway after learning all that I decided to write my own "temorary file temporary redirection code" as there is no Perl magic that redirects a write to a file descriptor to a scalar. I guess writing to some shared POSIX memory and then memory-mapping the SHM into a scalar could do the trick somewhat. So I#m accepting this answer as it seems the only way to go, even if not the way desired initially. – U. Windl Mar 09 '21 at 06:58
  • @U. Windl, No, IPC::Run doesn't use named pipes (on Linux). Just plain old pipe(2) pipes. – ikegami Mar 09 '21 at 07:13
  • @U. Windl, Re "*as there is no Perl magic*", Correct, `sh` has no "Perl magic" allowing it to write to other process's address space. – ikegami Mar 09 '21 at 07:14
  • @U. Windl, No, memory mapping would not work for a number of reasons, start with lack of synchronization. – ikegami Mar 09 '21 at 07:16
  • How would you even map a process's standard output to a region of shared memory, anyways? – Shawn Mar 09 '21 at 08:43