目录
proc_open 与 popen 类似,具体可以通过链接自行查看,最大的区别也如文档所诉:
If you’re looking for bi-directional support (two-way), use proc_open().
简单示例
popen
$handle = popen("/bin/ls", "r");
$read = fread($handle, 2096);
echo $read;
pclose($handle);
proc_open
<?php
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => STDOUT
);
$env = array('some_option' => 'a');
$pwd = '/tmp';
$process = proc_open('/usr/bin/php', $descriptorspec, $pipes,$pwd, $env);
if (is_resource($process)) {
fwrite($pipes[0], '<?php var_dump(getenv()); ?>');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
}
?>
坑
上面proc_open的例子是可以运行的,但我希望他是可以被复用的,封装了一个函数,修改以上的例子,代码如下:
function run(&$descriptorspec ,&$pipes){
$env = array('some_option' => 'a');
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => STDOUT
);
$pwd = '/tmp';
$process = proc_open('/usr/bin/php', $descriptorspec, $pipes,$pwd, $env);
}
run($descriptorspec ,$pipes);
fwrite($pipes[0], '<?php var_dump(getenv()); ?>');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
如标题所示,这是个坑,它是无法运行的,会出现以下错误:
PHP Warning: fwrite(): supplied resource is not a valid stream resource in /proc_poen.php on line 19
PHP Warning: fclose(): supplied resource is not a valid stream resource in /proc_poen.php on line 20
PHP Warning: stream_get_contents(): supplied resource is not a valid stream resource in proc_poen.php on line 22
PHP Warning: fclose(): supplied resource is not a valid stream resource in proc_poen.php on line 24
当时猜测这个错误的原因:
- proc_open 没执行成功
- $pipes 未能正常初始化
- $pipes 未能传递出来
逐一排查后,发现都是不是,这里直接给可以运行的版本:
function run(&$descriptorspec ,&$pipes){
$env = array('some_option' => 'a');
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => STDOUT
);
$pwd = '/tmp';
return $process = proc_open('/usr/bin/php', $descriptorspec, $pipes,$pwd, $env);
}
$process = run($descriptorspec ,$pipes);
fwrite($pipes[0], '<?php var_dump(getenv()); ?>');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
原因
我们先看下 proc_open 的返回值
proc_open 返回值
Returns a resource representing the process, which should be freed using proc_close() when you are finished with it. On failure returns false.
失败时,会返回 false,成功时会返回代表该进程的资源,用于完成后释放它。
坑在哪
对于编码习惯不好的我来说,有时候一些变量的释放会交给php。而这个对我来说极为陌生的函数,我只处理了我需要的部分——它生成的管道。正式这个原因,我一脚踩进了坑了,并且爬了许久。那么到底发生了什么?
当我把proc_open放入到函数中,其返回值的作为域也就是在函数内,当函数完成后,$process 没有被使用,则自动被释放。与此同时其创建的管道也会被关闭。因此,这里只传递 $pipes 是不够的,还需要把返回值 $process也传递出来。
补充
后面才发现,评论里面就有类似问题:
Interestingly enough, it seems you actually have to store the return value in order for your streams to exist. You can't throw it away.
In other words, this works:
<?php
$proc=proc_open("echo foo",
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
print stream_get_contents($pipes[1]);
?>
prints:
foo
but this doesn't work:
<?php
proc_open("echo foo",
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
print stream_get_contents($pipes[1]);
?>
outputs:
Warning: stream_get_contents(): <n> is not a valid stream resource in Command line code on line 1
The only difference is that in the second case we don't save the output of proc_open to a variable.
https://www.php.net/manual/zh/function.proc-open.php#97379