Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Respond to questions asked by a program run on the command line in PHP

I want to know how I can interact with a program that I run in a command line PHP script. The scenario is:

  1. Start executing a program.
  2. Read the output until a question is being asked (by reading STDOUT I guess).
  3. Type the answer and press Enter (by writing to STDIN I guess). The user is not to input this, the script already knows what to answer by reading and interpreting the output from step 2.
  4. Again read the output until a new question is being asked.
  5. Again type the answer and press Enter. Again, the script knows it all, no user input is to take place.
  6. This question/answer scenario repeats x number of times until the program is finished.

How can I write a PHP script that does this? I'm thinking that I probably want to use proc_open() but I can't figure out how. I'm thinking it would be something like this but it doesn't work of course:

$descriptorspec = array(
    0 => array('pipe', 'r'),  //STDIN
    1 => array('pipe', 'w'),  //STDOUT
    2 => array('pipe', 'r'),  //STDERR
);
$process = proc_open('mycommand', $descriptorspec, $pipes, null, null);
if (is_resource($process)) {
    // Get output until first question is asked
    while ($buffer = fgets($pipes[1])) {
        echo $buffer;
    }
    if (strpos($buffer, 'STEP 1:') !== false) {
        fwrite($pipes[0], "My first answer\n");  //enter the answer
    } else {
        die('Unexpected last line before question');
    }

    // Get output until second question is asked
    while ($buffer = fgets($pipes[1])) {
        echo $buffer;
    }
    if (strpos($buffer, 'STEP 2:') !== false) {
        fwrite($pipes[0], "My second answer\n");  //enter the answer
    } else {
        die('Unexpected last line before question');
    }

    // ...and so we continue...
} else {
    echo 'Not a resource';
}

UPDATE: I figured out that the program outputs the questions to STDERR (because it writes STDOUT to a file).

like image 827
TheStoryCoder Avatar asked Mar 06 '16 08:03

TheStoryCoder


1 Answers

You are certainly on the right track.

It might be best to start with the glaring issue in your code:

while ($buffer = fgets($pipes[1])) {
    echo $buffer;
}

This loop will never exit. At some point the program is going to ask you a question, but your code at this point is still executing the (blocking) fgets call.

As to how to write code that works properly....

The most obvious solution is not to bother waiting for the prompt before providing your answer. This will work as long as:

  1. You don't need to adapt your response based on the preceding output of the program

  2. The program is reading from its stdin and does not clear the buffer at any point

In fact, you don't even need a controlling process to this:

program <input.txt >output.txt 2>errors.txt

But assuming 1 and/or 2 do not apply, and given that it is redirecting its stdout already (which kind of suggests there's more to the story than we know about) then,

...
if (is_resource($process)) {
   while ($buffer=fgets($pipes[2]) { // returns false at EOF/program termination
       if (strpos($buffer, 'STEP 1:') !== false) {
           fwrite($pipes[0], "My first answer\n");      
       } else if (strpos($buffer, 'STEP 2:') !== false) {
           fwrite($pipes[0], "My second answer\n");  //enter the answer
       }
   }
}

Implementing checks for out of sequence questions and branching in the request/response cycle are left as an exercise for the reader.

like image 75
symcbean Avatar answered Oct 07 '22 17:10

symcbean