PHP Conference Japan 2024

popen

(PHP 4, PHP 5, PHP 7, PHP 8)

popen打开进程文件指针

描述

popen(字符串 $command, 字符串 $mode): 资源|false

打开一个到进程的管道,该进程通过分叉 command 给出的命令来执行。

参数

command

命令

mode

模式。读取为 'r',写入为 'w'

在 Windows 上,popen() 默认为文本模式,即写入或读取到管道的任何 \n 字符将被转换为 \r\n。如果不需要这样做,可以通过分别将 mode 设置为 'rb''wb' 来强制使用二进制模式。

返回值

返回一个与 fopen() 返回的文件指针相同的指针,除了它是单向的(只能用于读取或写入)并且必须使用 pclose() 关闭。此指针可与 fgets()fgetss()fwrite() 一起使用。当模式为 'r' 时,返回的文件指针等于命令的 STDOUT,当模式为 'w' 时,返回的文件指针等于命令的 STDIN。

如果发生错误,则返回 false

范例

示例 #1 popen() 示例

<?php
$handle
= popen("/bin/ls", "r");
?>

如果要执行的命令找不到,则会返回一个有效的资源。这可能看起来很奇怪,但这是有道理的;它允许您访问 shell 返回的任何错误消息。

示例 #2 popen() 示例

<?php
error_reporting
(E_ALL);

/* 添加重定向以便我们可以获取 stderr。 */
$handle = popen('/path/to/executable 2>&1', 'r');
echo
"'$handle'; " . gettype($handle) . "\n";
$read = fread($handle, 2096);
echo
$read;
pclose($handle);
?>

注释

注意:

如果您正在寻找双向支持(双向),请使用 proc_open()

参见

  • pclose() - 关闭进程文件指针
  • fopen() - 打开文件或 URL
  • proc_open() - 执行命令并打开用于输入/输出的文件指针

添加注释

用户贡献的注释 24 条注释

webmaster at php-idee dot de
15 年前
如果您尝试在 Windows 下执行命令,则 PHP 脚本通常会等到进程终止。即使您不想等待进程结束,执行长期运行的进程也会暂停 PHP 脚本。

找到如何在 Windows 下启动进程而不等待其终止的这个很好的示例并不容易。

<?php
$commandString
= 'start /b c:\\programToRun.exe -attachment "c:\\temp\file1.txt"';
pclose(popen($commandString, 'r'));
?>
jlh
8 年前
不要期望当可执行文件根本不存在时,此函数返回 false。仍然会打开一个流,但无法从中读取任何内容。类似于“sh: 1: asdfasdfasdf: not found”的错误将打印到 STDERR。

解决方案 1:查看 pclose() 的返回值,它将是运行命令的 shell 的退出状态。在 Linux 上,如果找不到可执行文件,则为 127。否则,它是可执行文件本身的退出状态。

解决方案 2:改用 proc_open(),它允许捕获 STDERR,然后解析它以查找错误。

您可能应该同时执行这两个操作。
anonymous at anon dot com
9 年前
作为 anonymous at anon dot com 提供的代码的旁注

$cmd = "php longscript.php";

function execInBackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
} else {
exec($cmd . " > /dev/null &");
}
}

我遇到了一个问题,即 Windows 会在整个脚本被解释之前过快地关闭调用,但我不想让我的主脚本挂起,直到它完全加载。

作为解决方法,我调用了一个小型 .php 脚本,然后该脚本会调用较大的脚本。

myfile.php
<?php
$cmd
= "php timewrapper.php";

function
execInBackground($cmd) {
if (
substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
} else {
exec($cmd . " > /dev/null &");
}
}
?>

timewrapper.php
<?php
$cmd
= "php longscript.php";
$timer = popen("start /B ". $cmd, "r");
sleep(30);
pclose($timer);
?>

这样我的主脚本就可以继续运行而无需暂停,而小脚本在加载较大的文件时会暂停。
匿名用户 (anon dot com)
13 年前
如果在 Windows 上,您需要启动一个需要管理员权限的批处理文件,则可以为该批处理文件创建一个快捷方式,单击属性,在其中一个属性页上选中“以管理员身份运行”,然后双击该快捷方式一次(以初始化“以管理员身份运行”业务)。

然后使用 popen("/path/to/shortcut.lnk") 将以管理员权限运行您的批处理文件。

当您想使用 cli php 执行一些长时间运行的任务并且该 php-cli 需要使用会话时非常方便。
rockytriton
17 年前
注意,在 Windows 中使用批处理文件时,必须在批处理文件的末尾添加“exit”,否则每次执行页面时,都会在您的进程列表中留下一个新的 cmd.exe。
atampone (NOSPAMFORME dot trdsupra dot com)
19 年前
如果您想在 Windows 下派生一个进程,则可以使用此函数。我创建了一个名为 runcmd.bat 的批处理文件,其中包含以下行

start %1 %2 %3 %4

然后我有了以下函数

<?php
define
('RUNCMDPATH', 'c:\\htdocs\\nonwebspace\\runcmd.bat');

function
runCmd($cmd) {
$externalProcess=popen(RUNCMDPATH.' '.$cmd, 'r');
pclose($externalProcess);
}
?>

有了这个,执行类似以下操作

<?php runCmd('php.exe printWorkOrder.php 3498'); ?>
将启动 Apache 之外的 php.exe,并允许调用 runCmd() 函数的脚本继续执行,而无需等待命令行进程返回。该进程将在 Apache(或您正在运行的任何 Web 服务器)运行的同一用户帐户下运行,因此请确保它具有执行所需操作的权限。此外,请确保批处理文件具有足够的 %n,以便传递您可能需要传递的所有命令行变量。

特别感谢来自 devshed 论坛的 kicken 提供了这个想法。
Marbug (gmail dot com)
15 年前
如果您想从 Linux 服务器下载大小超过 2GB 的文件,可以使用以下方法

<?php
function serveFile( $file , $as ){
header( 'Expires: Mon, 1 Apr 1974 05:00:00 GMT' );
header( 'Pragma: no-cache' );
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
header( 'Content-Description: File Download' );
header( 'Content-Type: application/octet-stream' );
header( 'Content-Length: '.trim(`stat -c%s "$file"`) );
header( 'Content-Disposition: attachment; filename="'. $as .'"' );
header( 'Content-Transfer-Encoding: binary' );
//@readfile( $file );

flush();
$fp = popen("tail -c ".trim(`stat -c%s "$file"`)." ".$file.' 2>&1', "r");
while(!
feof($fp))
{
// 将当前文件部分发送到浏览器
print fread($fp, 1024);
// 将内容刷新到浏览器
flush();
}
fclose($fp);
}
?>
rjl (xs4all dot nl)
19 年前
ps 命令输出被截断?

解决方案在于 ps 显示信息的方式
特别是 -w 选项,它
“使用 132 列显示信息,
而不是默认的窗口大小。”……
不知何故,在 php 中使用 fgets 会导致 74 个字符
无论 init 长度参数是什么

一些代码

<?php
echo '<table width="99%"><tr><td>cron</td></tr>' . "\n";
$fp=popen("/bin/ps -waux","r");
while (!
feof($fp)) {
$buffer = fgets($fp, 4096);
$croninf .= '<tr><td>' . $buffer . '</td></tr>' . "\n";
}
pclose($fp);
echo
$croninf;
echo
'</table><br><br>' . "\n";
?>

再见,

Rene =<>=
hacklor (NOSPAM [DOT] com)
14 年前
有一种简单的方法可以在后台启动进程,但仍然可以找出进程结果。我将下面一些用户的资料与我自己的资料结合起来,得出了以下结论

<?php
$bat_filename
= "C:\\my_bat_file.bat";
$bat_log_filename = "C:\\my_bat_file_bat.log";
$bat_file = fopen($bat_filename, "w");
if(
$bat_file) {
fwrite($bat_file, "@echo off"."\n");
fwrite($bat_file, "echo Starting proces >> ".$bat_log_filename."\n");
fwrite($bat_file, "php c:\\my_php_process.php >> ".$bat_log_filename."\n");
fwrite($bat_file, "echo End proces >> ".$bat_log_filename."\n");
fwrite($bat_file, "EXIT"."\n");
fclose($bat_file);
}

//
// 在后台启动进程
//
$exe = "start /b ".$bat_filename;
if(
pclose(popen($exe, 'r')) ) {
return
true;
}
return
false;
?>

在我的例子中,.bat 和 .log 文件的文件名并不总是相同的,所以我需要一种动态的方式来创建 .bat 文件。php 命令的输出使用 >> 命令保存到日志文件。所有打印和错误都存储在那里。稍后您可以打开日志文件并查看发生了什么。
cyberlot (cyberlot dot net)
22 年前
以下代码适用于双向处理;)祝大家玩得开心

<?php
system
("mkfifo pipeout");
$pipe = popen("./nwserver -module Chapter1E > pipeout","w");
$pipeout = fopen("pipeout", "r");
while (
$s = fgets($pipeout,1024)) {
echo
$s;
}

?>
antman3351
3 年前
注意使用 popen 和 start 在 Windows 上运行外部脚本而无需 php 等待。

例如
pclose( popen( 'start /b php someLongScript.php *> nul', 'rb' ) );

如果 start 找不到 exe,它将打开一个弹出消息,并且 pclose 将挂起,直到弹出窗口关闭。
erco (seriss dot com)
6 年前

使用 popen() 和 "w" 模式将命令的标准输出发送到浏览器的另一种解决方法

一个简单的解决方案是使用两个 php 脚本;"real.php" 中包含 popen($cmd, "w") 命令,另一个是 "wrapper.php",它只是一个简单的单行脚本,调用 system("php real.php");

从浏览器调用 "wrapper.php" 允许 "real.php" 中的 popen($cmd,"w") 按预期工作,这样 $cmd 的标准输出就会到达浏览器。如果尝试跳过包装器并直接运行 "real.php",则 $cmd 的标准输出将丢失到 /dev/null。
PGP Dude
19 年前
我应该说,我的主机使用的是修改版的安全模式,所以我不确定这是否会导致 "popen" 与 "proc_open" 出现问题。在启用安全模式的情况下,初始命令字符串后面的所有单词都被视为单个参数。因此,echo y | echo x 变成了 echo "y | echo x"。[正因为如此,]LinixDude010 的脚本对我来说不起作用。根据手册,使用 popen 进行读写似乎是不正确的。

该脚本生成了 pgp 文本,但文本存在问题,我无法对其进行解码。

这个替换脚本使用 proc_open,它可以读写,确实有效

<?php
function pgp_encrypt($keyring_location, $public_key_id, $plain_text) {
$encrypted_text='';
$key_id = EscapeShellArg($public_key_id);
putenv("PGPPATH=$keyring_location");

// 加密消息
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr ?? 而不是文件
);
$process = proc_open("pgpe -r $key_id -af", $descriptorspec, $pipes);
if (
is_resource($process)) {
fwrite($pipes[0], $plain_text);
fclose($pipes[0]);
while(
$s= fgets($pipes[1], 1024)) {
// 从管道读取
$encrypted_text .= $s;
}
fclose($pipes[1]);
// 可选:
while($s= fgets($pipes[2], 1024)) {
$encrypted_text.= "\n<p>Error: $s</p>\n";
}
fclose($pipes[2]);
}
return
$encrypted_text;
}

$message = pgp_encrypt("/home/username/.pgp", "[email protected]", "dummy text to be encrypted");
print
nl2br($message);

?>
http://vmlinuz.nl/about/contact/
22 年前
来自 popen linux 程序员手册

"命令参数是指向包含 shell 命令行的以空字符结尾的字符串的指针。此命令使用 -c 标记传递给 /bin/sh。"

由于 php 使用此 popen 函数,因此您需要确保 /bin/sh 存在。此文件可能在 chroot()ed 环境中不存在。
linuxdude010 at yahoo dot com
22 年前
我在使用 PGP 加密消息时遇到了各种问题,但我最终使其工作了。诀窍是“chmod o+r pubring.pkr”,以便 apache 服务器可以读取公钥!然后,此函数运行良好

<?PHP
function pgp_encrypt($keyring_location, $public_key_id, $plain_text) {

$key_id = EscapeShellArg($public_key_id);
putenv("PGPPATH=$keyring_location");

// 加密消息
$pipe = popen("pgpe -r $key_id -af", "r");
fwrite($pipe, $plain_text);
$encrypted_text = '';
while(
$s = fgets($pipe, 1024)) {
// 从管道读取
$encrypted_text .= $s;
}
pclose($pipe);

return
$encrypted_text;
}

$message = pgp_encrypt("/home/username/.pgp", "[email protected]", "dummy text to be encrypted");
print
nl2br($message);

?>
mrjake2
14 年前
如果您在 Debian "Squeeze" 上的 chroot'ed 环境中运行,则此命令将不起作用;内核代码中存在问题,popen() 最终会调用该代码。

请注意,pecl 大量使用此命令,因此,如果您在此环境中运行,则需要从源代码安装 pecl 扩展。
Cride5
19 年前
这是一个用于监控您的 http 访问日志的小脚本。

<?php

$handle
= popen("tail -f /etc/httpd/logs/access.log 2>&1", 'r');
while(!
feof($handle)) {
$buffer = fgets($handle);
echo
"$buffer<br/>\n";
ob_flush();
flush();
}
pclose($handle);

?>

----
www.eviltree.co.uk
www.solidsites.co.uk
www.mongbong.com
don at digithink dot com
18 年前
<?php
// 上面的导入函数可以使用
// /usr/local/bin/xls2csv(catdoc 的一部分)和 popen 轻松扩展
// 以直接读取 Excel 文件。
// 在我们的特定应用程序中,第一行是文件标题。
function importxls($file,$head=true,$throwfirst=true,$delim=",",$len=1000) {
$return = false;
$handle = popen("/usr/local/bin/xls2csv $file", "r");
// 或在不存在时终止。
if ($throwfirst) {
$throw = fgetcsv($handle, $len, $delim);
}
if (
$head) {
$header = fgetcsv($handle, $len, $delim);
}
while ((
$data = fgetcsv($handle, $len, $delim)) !== FALSE) {
if (
$head AND isset($header)) {
foreach (
$header as $key=>$heading) {
$row[$heading]=(isset($data[$key])) ? $data[$key] : '';
print
"<li>". $heading ."=>" . $row[$heading]."</li>";
}
$return[]=$row;
} else {
$return[]=$data;
}
}
fclose($handle);
return
$return;
}
?>
ajv-php at erkle dot org
22 年前
我注意到上面的一些示例似乎建议在没有双向 popen(在某些操作系统上)的情况下,通过管道 shell 转义将未加密的数据传递给 gpg。

我采用的方法类似于



<?php
$prefix
= 'example';
$command = '/usr/local/bin/gpg --encrypt --armor --no-tty --batch --no-secmem-warning --recipient "[email protected]"';
$tmpfile = tempnam('/tmp', $prefix);
$pipe = popen("$command 2>&1 >$tmpfile", 'w');
if (!
$pipe) {
unlink($tmpfile);
} else {
fwrite($pipe, $plaintxt, strlen($plaintxt));
pclose($pipe);
$fd = fopen($tmpfile, "rb");
$output = fread($fd, filesize($tmpfile));
fclose($fd);
unlink($tmpfile);
}
return
$output;
?>

这意味着未加密的信息不会通过(可能可读的)shell 命令传递,并且只有加密的信息才会存储在磁盘上。
php at keithtyler dot net
14 年前
请注意,您*必须*在句柄上执行读取操作才能使用 feof(),即使命令没有输出任何内容!所以…

<?php
$f
=popen("sleep 2","r");
while (!
feof($f)) {}
pclose($f);
print
"done";
?>

将永远无法完成。
nricciardi at mindspring dot com
22 年前
我尝试使用 popen 使用双向管道,但由于明显的原因没有成功,但我设法创建了一个简单的脚本解决了这个问题。此示例用于 gpg 加密。

<?php
$message
= "this is the text to encrypt with gpg";
$sendto = 'Dummy Key <[email protected]>';

system("mkfifo pipein");
system("mkfifo pipeout");
system("gpg --encrypt -a -r '$sendto' > pipeout < pipein &");
$fo = fopen("pipeout", "r");
$fi = fopen("pipein", "w");
fwrite($fi, $message, strlen($message));
fclose($fi);
while (!
feof($fo)) {
$buf .= fread($fo, 1024);
}
echo
$buf;
unlink("pipein");
unlink("pipeout");
?>

如果有人有更好的方法来做到这一点,我很乐意看到它。
betchern0t
17 年前
在长时间运行的子进程的情况下需要注意。假设您想运行 tail -f /var/log/messages 或在我的情况下刻录 DVD。如果您有繁忙等待,Apache2 可以保持在 100% cpu 附近并稳定地增加内存。在我的情况下,服务器在大约一个小时和 90% 的 DVD 刻录后崩溃了。在此期间,apache 消耗了 1GB 的交换空间。

违规代码 - 请勿复制

<?php
$ThisCommand
= sprintf("%s %s",COMMAND,$ThisFile);
$fp=popen($ThisCommand,"r");
while (!
feof($fp)) {
set_time_limit (20);
$results = fgets($fp, 4096);
if (
strlen($results) == 0) {
// 停止浏览器超时
echo " ";
flush();
} else {
$tok = strtok($results, "\n");
while (
$tok !== false) {
echo
htmlentities(sprintf("%s\n",$tok))."<br/>";
flush();
$tok = strtok("\n");
}
}
}
pclose($fp);
?>

要从零内存和 100% cpu 变为可忽略的内存和可忽略的 cpu,请添加一个 sleep。

<?php
while (!feof($fp)) {
set_time_limit (20);
$results = fgets($fp, 256);
if (
strlen($results) == 0) {
// 停止浏览器超时
echo " ";
flush();
} else {
$tok = strtok($results, "\n");
while (
$tok !== false) {
echo
htmlentities(sprintf("%s\n",$tok))."<br/>";
flush();
$tok = strtok("\n");
}
}
// 避免繁忙等待
sleep(1);

}
?>

我认为持续敲击空格以保持浏览器唤醒触发了 apache 中的一些问题。
Anonymous
22 年前
这是一个解决 php 中没有双向管道的变通方法。

如果您有双向管道支持,请不要使用此方法。

这里的技巧是在命令行上将输入发送到目标应用程序。特别是,我想使用 openssl 而无需使用临时文件或命名管道。此解决方案也应该是线程/进程安全的。

这在 Linux(RedHat 7)上有效。

<?php
function filterThroughCmd($input, $commandLine) {
$pipe = popen("echo \"$input\"|$commandLine" , 'r');
if (!
$pipe) {
print
"pipe failed.";
return
"";
}
$output = '';
while(!
feof($pipe)) {
$output .= fread($pipe, 1024);
}
pclose($pipe);
return
$output;
}

# 示例:
print filterThroughCmd("hello", "cat");
# 将管道连接到 cat 会产生回显输入的效果。
?>
Michel Machado
20 年前
另一个解决 php 中没有双向管道的变通方法。

<?php
$Cmd
=
"bc 2>&1 << END\n" .
"100+221\n" .
"1+3*3\n" .
"quit\n" .
"END\n";

$fp = popen($Cmd, 'r');
$read = fread($fp, 1024);
echo
$read;
pclose($fp);
?>
To Top