http://www.perlmonks.org?node_id=1056490

ric00015 has asked for the wisdom of the Perl Monks concerning the following question:

I am somewhat familiar with various ways of calling a script from another one. I don't really need an overview of each, but I do have a few questions. Before that, though, I should tell you what my goal is.

I am working on a perl/tk program that: a) gathers information and puts it in a hash, and b) fires off other scripts that use the info hash, and some command line args. Each of these other scripts are available on the command line (using another command-line script) and need to stay that way. So I can't just put all that into a module and call it good.I do have the authority to alter the scripts, but, again, they must also be usable on the command line.

The current way of calling the other script is by using 'do', which means I can pass in the hash, and use the same version of perl (I think). But all the STDOUT (and STDERR too, I think) goes to the terminal.

Here's a simple example to demonstrate the output:

this_thing.pl #!/usr/bin/env perl use strict; use warnings; use utf8; use Tk; my $mw = MainWindow->new; my $button = $mw->Button( -text => 'start other thing', -command => \&start, )->pack; my $text = $mw->Text()->pack; MainLoop; sub start { my $script_path = 'this_other_thing.pl'; if (not my $read = do $script_path) { warn "couldn't parse $script_path: $@" if $@; warn "couldn't do $script_path: $!" unless defined $read; warn "couldn't run $script_path" unless $read; } } this_other_thing.pl #!/usr/bin/env perl use strict; use warnings; use utf8; print "Hello World!\n";

How can I redirect the STDOUT and STDIN (for interactive scripts that need input) to the text box using the 'do' method? Is that even possible?

If I can't use the 'do' method, what method can redirect the STDIN and STDOUT, as well as enable passing the hash in and using the same version of perl?

Update! I talked with the one who originally created the scripts, and he agreed that jethro's suggestion of using modules and script wrappers was a good one. So that is what I am going to do. This ensures that I am not using different perls, I can route the output from the module anywhere I want, and passing that hash in is now very easy. Solves most, if not all, of my problems. Thanks, everyone, for your suggestions. See you around.

  • Comment on I want to run a script from another script, use the same version of perl, and reroute IO to a terminal-like textbox
  • Download Code

Replies are listed 'Best First'.
Re: I want to run a script from another script, use the same version of perl, and reroute IO to a terminal-like textbox
by jethro (Monsignor) on Oct 01, 2013 at 15:17 UTC

    This is more a suggestion how you could reach your goal some other way

    You could extract the code of the command line scripts to one or more library modules. Then change the scripts to use the library modules. The scripts would just interpret the command line parameters and feed them to the library function.

    Now your perl/tk program could use the library functions as well and would neither need to transfer the hash to the scripts nor fight with STDOUT and STDIN.

      That's a really good idea. I like using modules better than calling scripts, probably for all the right reasons. :) This also gets around the passing the hash problem too.

Re: I want to run a script from another script, use the same version of perl, and reroute IO to a terminal-like textbox
by kcott (Archbishop) on Oct 01, 2013 at 15:13 UTC

    G'day ric00015,

    Welcome to the monastery.

    For capturing STDOUT and STDERR, take a look at Capture::Tiny. It's SEE ALSO section lists many other similar modules which may be of interest.

    To get a filehandle to STDIN, consider open with '|-' mode.

    Look at Tk::fileevent to create non-blocking read/write callbacks for your filehandles.

    perlipc provides other options for inter-process communications.

    -- Ken

      Thanks for the welcome.

      Good ideas. Capture::Tiny isn't what I need because I may need to write to the child (asking for y/n sort of thing), but IO::Tee might be what I need. It seems simple, and allows for bi-directional communication. The only problem might be if it starts the child process. I need to be able to pass in the hash, and I'm not sure how to do that outside of 'do'.

      Same with your other suggestions. I need to be able to pass in the hash, as well as capture the output, and send some input.

Re: I want to run a script from another script, use the same version of perl, and reroute IO to a terminal-like textbox
by Perlbotics (Archbishop) on Oct 01, 2013 at 15:52 UTC

    I second what jethro said. Maybe use a Modulino (Example from 'Mastering Perl' - see link at end of article) ?

    But since you asked... (because it is possible, not recommended):

    use strict; use warnings; our $exchange = "before (set by caller)"; my $other_file = "this_other_thing.pl"; my ($out, $err) = redir_file( $other_file ); #-- proof of re-direction and import/export print "Caller: GOT from STDOUT ($other_file): ", join("\n\tOUT> ", "", split( /\n/, $out) ), "\n"; print "Caller: GOT from STDERR ($other_file): ", join("\n\tERR> ", "", split( /\n/, $err) ), "\n"; print "Caller: Exchange : $exchange\n"; sub redir_file { my $do_filename = shift; #-- we DUP the current STREAMS and then we close the original stream +s... open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!"; open(my $olderr, ">&STDERR") or die "Can't dup STDERR: $!"; close STDOUT; close STDERR; #-- now, we re-open the streams with redirection into strings... my $redir_stdout; my $redir_stderr; open( STDOUT, '>', \$redir_stdout ) or die "Cannot redirect STDOUT t +o string: $!"; open( STDERR, '>', \$redir_stderr ) or die "Cannot redirect STDERR t +o string: $!"; #-- this is like "do"... and exactly as insecure... my $do_file_content = qx{cat $do_filename}; # e.g. use Slurp... my $read = eval $do_file_content; # =8-O #... now $exchange might have been updated #-- now, we restore the previous streams... open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!"; open(STDERR, ">&", $olderr) or die "Can't dup \$olderr: $!"; return ($redir_stdout, $redir_stderr); }

    Modified this_other_thing.pl (our...)

    #!/usr/bin/env perl use strict; use warnings; use utf8; our $exchange; print STDOUT "PRG: Hello World!\n"; print STDERR "PRG: Hello Err!\n"; print "PRG: Exchange: ", ( $exchange // '(nothing)'), "\n"; $exchange = "after (called program)";

    Result:

    Caller: GOT from STDOUT (this_other_thing.pl): OUT> PRG: Hello World! OUT> PRG: Exchange: before (set by caller) Caller: GOT from STDERR (this_other_thing.pl): ERR> PRG: Hello Err! Caller: Exchange : after (called program)

    Don't know if this works well for Windows and in threaded/forked environments...

      The modulino concept looks neat, and that may even be the best solution, but I may not be able to do that. Especially if I have to change the name of the script.

      That code works. I had to massage it a bit to work with the Tk, and display in the text box, but it works. Can this also do stdin too? Some scripts may have y/n options that the user needs to select.

        Hello ric00015, redirecting STDIN is a little bit harder due to blocking I/O. You might be able to fork a sub-process in the TK program that feeds the external program (which is still running in the same parent process) -- ultra fragile!
        Since you can modify the external script, you might be able to read alternatively from STDIN or an @EXT_INPUT array exported from the Tk program to the other one -- and again -- better do not follow/implement this concept!

        I strongly suggest to refactor your Perl program collection.
        You get something that is easier to maintain and to test. It's a little bit more work in the beginning, though - but if you have to extend and maintain this collection for a while, it will return your investment soon.

        Your modified version of the script would use a module and than invoke the sub or object-method with parameters taken from the command-line or ENVironment. Your current programs become wrapper, not Modulinos.
        Perhaps you should also have a look at App::Cmd for further inspiration?

        Update: Perhaps also useful: OT: Rewrite or Refactor?

      Oh, and the environment is Linux, with a non-threading version of Perl.

Re: I want to run a script from another script, use the same version of perl, and reroute IO to a terminal-like textbox
by mprentice (Sexton) on Oct 01, 2013 at 15:01 UTC
    From the documentation for do, it doesn't look like it's really for capturing output. Would IPC::Run3 help you? I suppose you could manipulate file descriptors, though that could get hairy and sounds like a Bad Idea. I would try to rethink the problem in terms of IPC::Run3.

      I didn't see Run3, but IPC::Run certainly has all the capturing I could ever use. The problem is that I need to pass the hash, and I don't know how to do that outside of 'do'.

      The other problem is that I want all scripts called from the main script to use the same version of perl. I don't know if IPC::Run sets the perl version or environment for the child processes (although now I say it like that, it might).

      I too am against Bad Ideas, and in any case, I don't even know how to manipulate file handlers. So that's not happening :)

      I found ICP::Run3, but it seems it doesn't allow interaction with the sub-processes, which is necessary for some child scripts. All the other drawbacks from Run are also present in Run3. Thanks for the idea though! Keeps me on my toes.