回答程序在PHP的命令行上运行时提出的问题

时间:2023-02-07 01:12:18

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

我想知道如何与在命令行PHP脚本中运行的程序进行交互。的场景是:

  1. Start executing a program.
  2. 开始执行计划。
  3. Read the output until a question is being asked (by reading STDOUT I guess).
  4. 读输出,直到有人问问题(我猜是通过STDOUT)。
  5. 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.
  6. 输入答案并按回车键(我猜是通过写信给STDIN)。用户不需要输入这个,脚本已经通过阅读和解释步骤2的输出知道该回答什么。
  7. Again read the output until a new question is being asked.
  8. 再次读取输出,直到出现新的问题。
  9. Again type the answer and press Enter. Again, the script knows it all, no user input is to take place.
  10. 再次输入答案并按回车键。同样,脚本知道这一切,不需要进行任何用户输入。
  11. This question/answer scenario repeats x number of times until the program is finished.
  12. 这个问答场景重复了x次,直到程序完成。

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:

如何编写一个PHP脚本?我想我可能想要使用proc_open(),但是我不知道如何使用。我想它应该是这样的但它当然不起作用:

$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).

更新:我发现程序将问题输出到STDERR(因为它将STDOUT写入文件)。

2 个解决方案

#1


0  

Hopefully the following example based on your code above will be enough to get you started.

希望基于上述代码的下面示例足以让您开始学习。

I've tested it on Linux although you didn't specify which operating system you were using, so your mileage may vary if you're running something else.

我在Linux上测试过它,虽然您没有指定您使用的是哪个操作系统,所以如果您运行的是其他操作系统,您的里数可能会有所不同。

Because the command is piped to another process, the output will be buffered. This means we won't receive the prompts because the buffer waits forever for a linefeed before sending the current line and the prompts are not followed by linefeeds.

因为命令被管道传输到另一个进程,所以输出将被缓冲。这意味着我们不会接收到提示,因为在发送当前行之前,缓冲区将永远等待一个linefeed,而提示符后面没有linefeed。

Thanks to Chris and ignomueller.net's answers to Trick an application into thinking its stdin is interactive, not a pipe we can confuse the command into thinking it's talking to a terminal by passing it as an argument to script, which generates a typescript of the command's output. By saving the typescript to /dev/null, flushing output after each write (-f) and excluding start and done messages (-q) this means we can read the prompts as they are output.

感谢Chris和ignomueller.net的回答,诱使应用程序认为它的stdin是交互式的,而不是一个管道,我们可以通过将命令作为参数传递给脚本,从而将命令误认为是在与终端对话,从而生成命令输出的打字稿。通过将打字稿保存到/dev/null,在每次写入(-f)之后刷新输出,并排除开始和完成消息(-q),这意味着我们可以在输出提示时读取它们。

You specified the STDOUT from the command should be sent to a file, whereas the questions are prompted for on STDERR. This is an added complication because the logic I've used doesn't seem to work reading from STDERR. However, if you redirect STDOUT to a file within the -c parameter to script, and then redirect script's own STDERR to STDOUT, it all seems to work OK.

您指定了从命令中发出的STDOUT,应该发送到一个文件,而问题则提示在STDERR上。这是一个额外的复杂性,因为我所使用的逻辑似乎无法阅读STDERR。但是,如果您将STDOUT重定向到-c参数中的文件到脚本,然后将脚本自己的STDERR重定向到STDOUT,那么看起来一切正常。

$descriptorspec = array(
    0 => array('pipe', 'r'),  //STDIN
    1 => array('pipe', 'w'),  //STDOUT
    2 => array('pipe', 'r'),  //STDERR
);
// Avoid buffering by passing the command through "script"
$process = proc_open(
    'script -qfc "mycommand >mycommand.out" /dev/null 2>&1',
    $descriptorspec, $pipes, null, null);
if (is_resource($process)) {
    $buffer = "";
    // Read from the command's STDOUT until it's closed.
    while (!feof($pipes[1])) {
        $input = fread($pipes[1], 8192);
        $buffer .= $input;

        // Output what we've read for debugging. You'd want to add some logic here
        // instead to handle the input and work out the answers to the questions.
        echo $input;

        // Answer the questions when appropriate.
        // This won't work if your output ever includes "STEP 1:" other than when
        // prompting for a question. You might need some more robust logic here.
        if (preg_match("/\nSTEP 1:$/", $buffer)) {
            fwrite($pipes[0], "My first answer\n");
        } elseif (preg_match("/\nSTEP 2:$/", $buffer)) {
            fwrite($pipes[0], "My second answer\n");
        }
    }
    proc_close($process);
} else {
    echo 'Not a resource';
}

I've written the code this way because you specified that the answers to the prompts required "reading and interpreting the output". If the answers were all known in advance of running the program, the solution is much simpler. In this case, you can simply output all the answers straight away before starting to read the response. The command won't care that you didn't wait for the prompt before providing input. You might need to call stream_set_blocking($pipes[0], false); first in this case, I'm not 100% sure.

我这样编写代码是因为您指定了提示的答案需要“读取和解释输出”。如果在运行程序之前就知道了所有答案,那么解决方案就简单得多。在这种情况下,您可以在开始读取响应之前直接输出所有答案。该命令不会在意您在提供输入之前没有等待提示符。您可能需要调用stream_set_blocking($pipes[0], false);首先,我不能百分之百确定。

#2


0  

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.

这个循环永远不会退出。有时程序会问你一个问题,但此时你的代码仍然在执行(阻塞)fget调用。

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

    程序正在从它的stdin中读取数据,并且在任何时候都不清除缓冲区

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,

但假设1和/或2不适用,并且考虑到它已经在重定向它的stdout(这暗示了故事比我们知道的更多),

...
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.

在请求/响应周期中实现对序列问题的检查和分支是留给读者的练习。

#1


0  

Hopefully the following example based on your code above will be enough to get you started.

希望基于上述代码的下面示例足以让您开始学习。

I've tested it on Linux although you didn't specify which operating system you were using, so your mileage may vary if you're running something else.

我在Linux上测试过它,虽然您没有指定您使用的是哪个操作系统,所以如果您运行的是其他操作系统,您的里数可能会有所不同。

Because the command is piped to another process, the output will be buffered. This means we won't receive the prompts because the buffer waits forever for a linefeed before sending the current line and the prompts are not followed by linefeeds.

因为命令被管道传输到另一个进程,所以输出将被缓冲。这意味着我们不会接收到提示,因为在发送当前行之前,缓冲区将永远等待一个linefeed,而提示符后面没有linefeed。

Thanks to Chris and ignomueller.net's answers to Trick an application into thinking its stdin is interactive, not a pipe we can confuse the command into thinking it's talking to a terminal by passing it as an argument to script, which generates a typescript of the command's output. By saving the typescript to /dev/null, flushing output after each write (-f) and excluding start and done messages (-q) this means we can read the prompts as they are output.

感谢Chris和ignomueller.net的回答,诱使应用程序认为它的stdin是交互式的,而不是一个管道,我们可以通过将命令作为参数传递给脚本,从而将命令误认为是在与终端对话,从而生成命令输出的打字稿。通过将打字稿保存到/dev/null,在每次写入(-f)之后刷新输出,并排除开始和完成消息(-q),这意味着我们可以在输出提示时读取它们。

You specified the STDOUT from the command should be sent to a file, whereas the questions are prompted for on STDERR. This is an added complication because the logic I've used doesn't seem to work reading from STDERR. However, if you redirect STDOUT to a file within the -c parameter to script, and then redirect script's own STDERR to STDOUT, it all seems to work OK.

您指定了从命令中发出的STDOUT,应该发送到一个文件,而问题则提示在STDERR上。这是一个额外的复杂性,因为我所使用的逻辑似乎无法阅读STDERR。但是,如果您将STDOUT重定向到-c参数中的文件到脚本,然后将脚本自己的STDERR重定向到STDOUT,那么看起来一切正常。

$descriptorspec = array(
    0 => array('pipe', 'r'),  //STDIN
    1 => array('pipe', 'w'),  //STDOUT
    2 => array('pipe', 'r'),  //STDERR
);
// Avoid buffering by passing the command through "script"
$process = proc_open(
    'script -qfc "mycommand >mycommand.out" /dev/null 2>&1',
    $descriptorspec, $pipes, null, null);
if (is_resource($process)) {
    $buffer = "";
    // Read from the command's STDOUT until it's closed.
    while (!feof($pipes[1])) {
        $input = fread($pipes[1], 8192);
        $buffer .= $input;

        // Output what we've read for debugging. You'd want to add some logic here
        // instead to handle the input and work out the answers to the questions.
        echo $input;

        // Answer the questions when appropriate.
        // This won't work if your output ever includes "STEP 1:" other than when
        // prompting for a question. You might need some more robust logic here.
        if (preg_match("/\nSTEP 1:$/", $buffer)) {
            fwrite($pipes[0], "My first answer\n");
        } elseif (preg_match("/\nSTEP 2:$/", $buffer)) {
            fwrite($pipes[0], "My second answer\n");
        }
    }
    proc_close($process);
} else {
    echo 'Not a resource';
}

I've written the code this way because you specified that the answers to the prompts required "reading and interpreting the output". If the answers were all known in advance of running the program, the solution is much simpler. In this case, you can simply output all the answers straight away before starting to read the response. The command won't care that you didn't wait for the prompt before providing input. You might need to call stream_set_blocking($pipes[0], false); first in this case, I'm not 100% sure.

我这样编写代码是因为您指定了提示的答案需要“读取和解释输出”。如果在运行程序之前就知道了所有答案,那么解决方案就简单得多。在这种情况下,您可以在开始读取响应之前直接输出所有答案。该命令不会在意您在提供输入之前没有等待提示符。您可能需要调用stream_set_blocking($pipes[0], false);首先,我不能百分之百确定。

#2


0  

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.

这个循环永远不会退出。有时程序会问你一个问题,但此时你的代码仍然在执行(阻塞)fget调用。

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

    程序正在从它的stdin中读取数据,并且在任何时候都不清除缓冲区

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,

但假设1和/或2不适用,并且考虑到它已经在重定向它的stdout(这暗示了故事比我们知道的更多),

...
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.

在请求/响应周期中实现对序列问题的检查和分支是留给读者的练习。