popen

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

popen打开进程文件指针

描述

popen(string $command, string $mode): resource|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 个注释

28
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'));
?>
6
jlh
8 年前
不要指望此函数在可执行文件不存在的情况下返回 false。无论如何都会打开一个流,但无法从中读取任何内容。类似于 "sh: 1: asdfasdfasdf: not found" 的错误将被打印到 STDERR。

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

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

你可能应该两者都做。
8
anonymous at anon dot com
9 年前
作为匿名用户提供的代码的旁注

$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 中遇到了一个问题,即在解释整个脚本之前,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);
?>

这样,我的主脚本就可以继续运行,而不必暂停,而小型脚本会在加载较大的文件时暂停。
8
anonymous at anon dot com
13 年前
如果在 Windows 上,你需要启动需要管理员权限的批处理文件,那么你可以为批处理文件创建一个快捷方式,单击属性,检查其中一个属性页上的“以管理员身份运行”,然后双击快捷方式一次(初始化该“以管理员身份运行”操作)。

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

当你想使用 cli php 执行一些长时间运行的任务,而该 php-cli 需要使用会话时,这很方便。
5
rockytriton
16 年前
注意,在 Windows 中使用批处理文件时,你必须在批处理文件的末尾添加一个“exit”,否则每次执行页面时,你都会在进程列表中得到一个新的 cmd.exe。
1
atampone at 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'); ?>
将启动 php.exe,使其在 Apache 之外运行,并允许调用 runCmd() 函数的脚本继续运行,而无需等待命令行进程返回。该进程将在 Apache(或你正在运行的任何 Web 服务器)运行的相同用户帐户下运行,因此请确保它具有执行你所需操作的权限。此外,请确保批处理文件具有足够的 %n,以便传递你可能需要传递的所有命令行变量。

特别感谢 devshed 论坛的 kicken 提出这个想法。
2
Marbug at gmail dot com
15 年前
如果你想从 Linux 服务器下载大于 2 GB 的文件,可以使用以下方法

<?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);
}
?>
1
rjl at xs4all dot nl
18 年前
ps 命令的输出被截断了?

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

一些代码

<?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";
?>

Ciao,

Rene =<>=
1
hacklor [AT] 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 命令的输出通过 >> 命令保存到日志文件中。所有打印和错误都存储在那里。以后你可以打开日志文件查看发生了什么。
1
cyberlot at 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;
}

?>
1
antman3351
2 年前
对于使用 popen 和 start 在 Windows 下运行外部脚本而不让 PHP 等待的用户注意。

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

如果 start 找不到 exe,它将打开一个弹出消息,pclose 将一直挂起,直到弹出窗口关闭。
1
erco at seriss dot com
5 年前
使用 popen() 和 "w" 模式以使命令的 stdout 达到浏览器的另一种解决方法

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

从浏览器调用 “wrapper.php” 允许 “real.php” 中的 popen($cmd,"w") 按预期工作,这样 $cmd 的 stdout 就可以到达浏览器。如果你尝试跳过包装器,只运行 “real.php”,$cmd 的 stdout 将丢失到 /dev/null。
0
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 ?? instead of a file
);
$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);

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

"command 参数是指向包含 shell 命令行的空终止字符串的指针。该命令使用 -c 标记传递给 /bin/sh。"

由于 php 使用此 popen 函数,因此您需要确保 /bin/sh 存在。此文件可能在 chroot()ed 环境中不存在。
0
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);

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

请注意,pecl 广泛使用此命令,因此如果您在此环境中运行,则需要从源代码安装 pecl 扩展。
-2
Cride5
18 年前
以下是一个用于监控您的 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
-2
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;
}
?>
-2
ajv-php at erkle dot org
22 年前
我注意到上面的一些示例似乎建议将未加密的数据通过管道 shell 转义传递给 gpg,在没有双向 popen(在某些操作系统上)的情况下。

我采取的方法类似于

<?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 命令传递,只有加密的信息才会存储在磁盘上。
-3
php at keithtyler dot net
14 年前
请注意,即使命令没有输出任何内容,您也*必须*对句柄进行读取才能使用 feof()! 所以..

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

永远不会完成。
-3
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");
?>

如果有更好的方法,我很乐意看到。
-2
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 使用率,请添加一个睡眠。

<?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 中引发了一些问题。
-3
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;
}

# example:
print filterThroughCmd("hello", "cat");
# 将管道传递给 cat 会对输入进行回显。
?>
-5
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