8

我正在使用我编写的包装类运行第三方脚本,该类调用shell_exec()和管道进入我稍后使用 php 代码解析的文件。我应该提到这是有效的,但我正在尝试增强功能,遇到了一个我没有想到的用例。

如何最好地管理 shell_exec() 的超时?我正在考虑将它包装在 atry() catch()但我不确定如何最好地处理时间组件。

我在这里阅读了一些与 and 相关的问题,shell_exec()似乎exec()通过将输出参数传递给exec()您可以获得返回,但这确实依赖于以返回状态完成的脚本。另外,在我的迷你测试页面中,我似乎无法让它返回任何输出!

我想到的另一个选项是使用模式对话框,在脚本工作时使用 ajax 样式微调器,并在 javascript 中设置手动超时。然后给用户一个关于它失败/超时和结束的模型对话框消息。

此用例是否有任何可接受的方法?

我的迷你测试包括以下内容,

public $e_return = array();
public $e_status = '';
// Paths are absolute from /
public function execCheck($domain){
    exec($this->ssl_check_path." -s ".$domain." -p 443 > ".$this->folder.$this->filename." 2>&1 &", &$this->e_return, &$this->e_status);
}

// Returns
Array
(
)

0

使用这个问题作为参考, Can't execute PHP script using PHP exec

http://www.php.net/manual/en/function.exec.php

4

4 回答 4

19

我为此类任务编写了一些工作代码。函数返回退出代码(0 - OK,>0 - 错误)并将 stdout、stderr 写入引用变量。

/*execute program and write all output to $out
terminate program if it runs more than 30 seconds */
execute("program --option", null, $out, $out, 30);
echo $out;

function execute($cmd, $stdin=null, &$stdout, &$stderr, $timeout=false)
{
    $pipes = array();
    $process = proc_open(
        $cmd,
        array(array('pipe','r'),array('pipe','w'),array('pipe','w')),
        $pipes
    );
    $start = time();
    $stdout = '';
    $stderr = '';

    if(is_resource($process))
    {
        stream_set_blocking($pipes[0], 0);
        stream_set_blocking($pipes[1], 0);
        stream_set_blocking($pipes[2], 0);
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }

    while(is_resource($process))
    {
        //echo ".";
        $stdout .= stream_get_contents($pipes[1]);
        $stderr .= stream_get_contents($pipes[2]);

        if($timeout !== false && time() - $start > $timeout)
        {
            proc_terminate($process, 9);
            return 1;
        }

        $status = proc_get_status($process);
        if(!$status['running'])
        {
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);
            return $status['exitcode'];
        }

        usleep(100000);
    }

    return 1;
}
于 2012-11-08T11:12:14.797 回答
8

我建议你考虑使用proc_open. 您可以将其配置为返回流资源,手动保持计时器,如果计时器在流程完成之前到期,您可以使用proc_terminate. 如果它在计时器到期之前完成,那么您可以使用proc_closethenstream_get_contents来获取本来会写入 stdout 的数据。

http://www.php.net/manual/en/function.proc-open.php

于 2010-08-04T17:15:10.393 回答
0

我试过了popen(),但之后没有办法终止进程。此外,stream_get_contents()即使在 Windows 上使用 stream_set_blocking 也会阻止流,所以我不得不使用 fread 代替。此外,proc_terminate 在 Windows 上不能正常工作,所以我不得不使用替代的 kill 函数。

我想出了这个,它现在应该可以在 Windows 和 Linux 上运行:

function execute($command, $timeout = 5) {
    $handle = proc_open($command, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipe);

    $startTime = microtime(true);

    /* Read the command output and kill it if the proccess surpassed the timeout */
    while(!feof($pipe[1])) {
        $read .= fread($pipe[1], 8192);
        if($startTime + $timeout < microtime(true)) break;
    }

    kill(proc_get_status($handle)['pid']);
    proc_close($handle);

    return $read;
}

/* The proc_terminate() function doesn't end proccess properly on Windows */
function kill($pid) {
    return strstr(PHP_OS, 'WIN') ? exec("taskkill /F /T /PID $pid") : exec("kill -9 $pid");
}
于 2016-10-27T11:26:21.513 回答
0

如果您使用的是 Linux(或 Windows 10 中的 WSL),那么使用 timeout 命令似乎是最简单的方法。看到这个答案: exec() with timeout

于 2020-10-02T23:00:31.240 回答