2

I have a Perl script to call another program. One section looks like this:

open(AMPL,"|ampl");
select(AMPL);
printf "option solver cplex;\n";
printf "var x1;\n"
printf "var x2;\n"
printf "minimize z: 3*x1 + 2*x2;\n";
printf "another_command;\n";
printf "data $ARGV[0];\n";
# More printfs
close(AMPL);

This code fails silently if instructions passed to ampl (that is, AMPL) are incorrect. Can the failures be either printed to STDERR or otherwise caught to abort the script?


EDIT: To clarify, this code pipes into an interactive session with the AMPL interpreter:

$ ampl
ampl: option solver cplex;
>>> var x1;
>>> var x2;
>>> minimize z: 3*x1 + 2*x2;
>>> another_command;
>>> data foo;
Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
Arya McCarthy
  • 8,554
  • 4
  • 34
  • 56
  • "Incorrect" for ampl? Then that's ampl's business. You are feeding an external program with data. It can do whatever it wants. It may exit with particular codes and you can catch that, but if it doesn't there is not much that the calling code can do. Do you have control over ampl source? – zdim Apr 09 '17 at 02:01
  • `ampl`'s exit code will be nonzero when there's an error; still, I figure if I'm piping things into `ampl`'s STDIN, I can read things back from its STDERR (or STDOUT). – Arya McCarthy Apr 09 '17 at 02:05
  • See [How to read to and write from a pipe in Perl](http://stackoverflow.com/questions/10765311/how-to-read-to-and-write-from-a-pipe-in-perl) (they asked about getting stdout but getting stderr is similar). – ThisSuitIsBlackNot Apr 09 '17 at 02:16
  • Since you mention `STDERR` I take it that the `ampl` _doesn't_ write to it on error, otherwise it would wind up on the terminal. That's a problem. If, instead, you want to catch it in the calling program (rather than leaving it to go wherever it wants), then there are many posts about that (redirect X from an external program), for example the one [ThisSuitIsBlackNot](http://stackoverflow.com/users/176646/thissuitisblacknot) linked. – zdim Apr 09 '17 at 02:38
  • Also, there are modules ... see for example [IPC::Run3](http://search.cpan.org/~rjbs/IPC-Run3-0.048/lib/IPC/Run3.pm) (and the linked post shows `IPC::Open3`) – zdim Apr 09 '17 at 02:44
  • Right--though I may have more than one command that needs to be a part of a single interactive AMPL session. – Arya McCarthy Apr 09 '17 at 02:45
  • I don't understand that "_more than one command_" (not an AMPL user), but I can post a short example of how to use `IPC::Run3`. With a module like that you know for fact that you are hearing back from `AMPL`. But it'd be good to know what "more than one command" means ...? – zdim Apr 09 '17 at 04:27
  • That'd actually be great. I updated my question to show what "more than one command means": streaming commands into AMPL the same way one would stream them into the Python or `irb` REPL. – Arya McCarthy Apr 09 '17 at 04:32

1 Answers1

3

Use a module, for example IPC::Run3, to get all that the calling program prints to streams.

use warnings;
use strict;

use IPC::Run3;

my @cmd = ('xargs', '0', 'cat');  # display a file which name is piped in
#my @cmd = ('wc', 'c');            # count characters in the passed string

my $cmd_stdin = shift || $0;      # file name, from cmdline or this script

run3 \@cmd, [$cmd_stdin], \my $out, \my $err;

print $out if $out;  # Whatever was written by command to STDOUT
print $err if $err;  # ... and to STDERR

If the invoked program does not write to standard streams then that's out of caller's hands.

In order to include a check of whether the command itself worked see this post.


With the question update, to feed strings to STDIN of AMPL

my @cmd = ('ampl');

my @stdin = ("arg_1\n", "arg_2\n", ..., "data $ARGV[0];\n");

run3 \@cmd, \@stdin, undef, \my $stderr;

The syntax requires that the input be an arrayref. The program's output is left to go to STDOUT by setting undef above, since if it's collected into $out variable it is not seen in real time.

Community
  • 1
  • 1
zdim
  • 64,580
  • 5
  • 52
  • 81
  • This is really illuminating. Where do the `printf`s fit into this? – Arya McCarthy Apr 09 '17 at 04:40
  • @aryamccarthy Added code for your case. Your `printf` feed the program, right? Here that is done by the module -- it pipes whatever is in `@input` into the program it invokes (the syntax is to use `\@input`). It also takes whatever the program prints and puts it into `$out` (and `$err`, if any). – zdim Apr 09 '17 at 04:46
  • I've updated the question again to show the intent: this isn't passing command-line arguments to an invocation of `ampl`. It's interacting with the AMPL interpreter. – Arya McCarthy Apr 09 '17 at 04:54
  • @aryamccarthy Thanks for the reminder, adjusted some statements and code. The module feeds the `STDIN` ... I am checking whether _interactive_ actually works. – zdim Apr 09 '17 at 05:19
  • 1
    @aryamccarthy Corrected: need `undef` (for stdout) to see the output of the invoked program on `STDOUT`. (The `\undef` that I had sends it to `/dev/null` instead.) – zdim Apr 09 '17 at 05:38