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

Posted

技术标签:

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

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

    开始执行程序。 阅读输出,直到有人提出问题(我猜是通过阅读 STDOUT)。 输入答案并按 Enter(我猜是写到 STDIN)。用户不要输入这个,脚本已经通过阅读和解释步骤 2 的输出知道要回答什么。 再次阅读输出,直到提出新问题。 再次输入答案并按 Enter。同样,脚本知道这一切,不会发生用户输入。 此问答场景重复 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 写入文件)。

【问题讨论】:

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

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

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

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

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

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

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

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

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

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

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% 确定。

【讨论】:

以上是关于回答在 PHP 命令行上运行的程序提出的问题的主要内容,如果未能解决你的问题,请参考以下文章

命令在命令行上运行良好,但不能从任务计划程序运行

浏览器上的推送通知工作不在命令行上

在 Windows 命令行上查找 PHP 版本

在命令行上为 IronPython 安装 pip

如何在命令行上使用 NMAKE?

在 windows 的命令行上创建一个空文件(如 linux touch 命令)