【问题标题】:Respond to questions asked by a program run on the command line in PHP回答在 PHP 命令行上运行的程序提出的问题
【发布时间】:2016-03-06 08:04:30
【问题描述】:

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

  1. 开始执行程序。
  2. 阅读输出,直到有人提出问题(我猜是通过阅读 STDOUT)。
  3. 输入答案并按 Enter(我猜是写到 STDIN)。用户不要输入这个,脚本已经通过阅读和解释步骤 2 的输出知道要回答什么。
  4. 再次阅读输出,直到提出新问题。
  5. 再次输入答案并按 Enter。同样,脚本知道这一切,不会发生用户输入。
  6. 此问答场景重复 x 次,直到程序完成。

如何编写一个 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';
}

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

【问题讨论】:

  • @jsxqf 您没有阅读我的问题。这与我与 PHP 交互无关。这是关于 PHP 与另一个程序交互(没有我的任何输入)。
  • 您的外部程序是否在“STEP 1:”和“STEP 2:”之后以及预期响应之前立即输出换行符(“\n”)?另外,问题总是相同且顺序相同吗?
  • 我认为套接字将是同样的好选择?
  • 你在什么操作系统上运行代码?

标签: php command-line-interface execution


【解决方案1】:

你肯定是在正确的轨道上。

最好从代码中明显的问题开始:

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

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

至于如何编写能正常工作的代码....

最明显的解决方案是在提供答案之前不必等待提示。只要:

  1. 您无需根据程序前面的输出调整您的响应

  2. 程序正在从其标准输入读取数据,并且在任何时候都不会清除缓冲区

事实上,您甚至不需要对此进行控制:

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

但假设 1 和/或 2 不适用,并且鉴于它已经在重定向其标准输出(这表明故事中的内容比我们所知道的要多),那么

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

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

【讨论】:

    【解决方案2】:

    希望以下基于您上面的代码的示例足以帮助您入门。

    我已经在 Linux 上进行了测试,尽管您没有指定您使用的是哪个操作系统, 因此,如果您正在运行其他项目,您的里程可能会有所不同。

    因为命令通过管道传送到另一个进程,所以输出将被缓冲。 这意味着我们不会收到提示,因为缓冲区永远等待 发送当前行前换行且提示后面不跟 换行。

    感谢 Chris 和 ignomueller.net's 回复Trick an application into thinking its stdin is interactive, not a pipe 我们可以通过传递将命令混淆为认为它正在与终端交谈 它作为script 的参数,它会生成命令输出的打字稿。 通过将打字稿保存到 /dev/null,每次写入后刷新输出 (-f) 和 不包括开始和完成消息 (-q) 这意味着我们可以阅读提示,因为它们 是输出。

    您指定命令中的 STDOUT 应发送到文件,而 在 STDERR 上会提示问题。这是一个额外的复杂性,因为 我使用的逻辑似乎无法从 STDERR 读取。但是,如果您重定向 STDOUT到-c参数内的一个文件到script,然后重定向script自己的 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';
    }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-29
      • 1970-01-01
      • 2019-08-27
      • 1970-01-01
      • 1970-01-01
      • 2010-11-08
      • 2017-09-10
      • 1970-01-01
      相关资源
      最近更新 更多