popen
(PHP 4, PHP 5, PHP 7, PHP 8)
popen — プロセスへのファイルポインタをオープンする
説明
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");
?>
実行すべきコマンドが見つからない場合には、正常なリソースが返されます。 おかしなことと思われるかもしれませんが、これには意味があります。 これによってシェルから返されたエラーメッセージにアクセスすることができるのです。
例2 popen() の例
<?php
error_reporting(E_ALL);
/* リダイレクトにより、標準エラー出力を取得します */
$handle = popen('/path/to/executable 2>&1', 'r');
echo "'$handle'; " . gettype($handle) . "\n";
$read = fread($handle, 2096);
echo $read;
pclose($handle);
?>
注意
注意:
双方向(two-way)のサポートを求めているのなら、 proc_open() を使用してください。
参考
- pclose() - プロセスのファイルポインタをクローズする
- fopen() - ファイルまたは URL をオープンする
- proc_open() - コマンドを実行し、入出力用にファイルポインタを開く
User Contributed Notes 24 notes
If you try to execute a command under Windows the PHP script normally waits until the process has been terminated. Executing long-term processes pauses a PHP script even if you don't want to wait for the end of the process.
It wasn't easy to find this beautiful example how to start a process under Windows without waiting for its termination:
<?php
$commandString = 'start /b c:\\programToRun.exe -attachment "c:\\temp\file1.txt"';
pclose(popen($commandString, 'r'));
?>
Don't expect this function to return false when the executable doesn't exist in the first place. A stream will be opened anyway but nothing can be read from it. An error similar to "sh: 1: asdfasdfasdf: not found" will be printed to STDERR.
Solution 1: Look at the return value of pclose(), it will be the exit status of the shell that runs the command. On Linux it will be 127 if the executable wasn't found. Otherwise it's the exit status of the executable itself.
Solution 2: Use proc_open() instead, which allows to also capture STDERR and then parse it for errors.
You probably should do both.
As a side note to the code provided by 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 &");
}
}
I had a problem where Windows would close the call too fast before the entire script was interpreted, but I didn't want my main script to hang until it would be fully loaded.
As a workaround, I called a tiny .php script which would then call the larger script.
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);
?>
This way my main script would continue to run without having to pause, while the tiny script pauses while it loads the larger file.
If, on windows, you need to start a batch file that needs administrator privileges, then you can make a shortcut to the batch file, click properties, check to on "run as administrator" on one of the property pages, and then double-click the shortcut once (to initialize that "run as administrator" business).
using popen("/path/to/shortcut.lnk") will then run your batch file with administrator privileges.
handy for when you want to use cli php to do some long running tasks and that php-cli needs to use sessions..
Note, when using this with a batch file in windows, you must put an "exit" at the end of your batch file or you will get a new cmd.exe stuck in your process list every time you execute the page.
If you want to fork a process under windows, this is the function to use. I created a batch file called runcmd.bat with the following line
start %1 %2 %3 %4
then I have the folowing function
<?php
define('RUNCMDPATH', 'c:\\htdocs\\nonwebspace\\runcmd.bat');
function runCmd($cmd) {
$externalProcess=popen(RUNCMDPATH.' '.$cmd, 'r');
pclose($externalProcess);
}
?>
with this, doing something like
<?php runCmd('php.exe printWorkOrder.php 3498'); ?>
will launch php.exe outside of apache and allow the script calling the runCmd() function to continue without waiting for the command line process to return. The process will run under the same user account that Apache (or whatever webserver you're running) is running under, so make sure it has permissions to do whatever you need to do. Also, make sure that the batch file has enough %n s in order to pass all the command line variables that you might need to pass.
Special thanks to kicken from the devshed forums for coming up with the idea.
If you want to download files from a linux server with a filesize bigger than 2GB you can use the following:
<?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))
{
// send the current file part to the browser
print fread($fp, 1024);
// flush the content to the browser
flush();
}
fclose($fp);
}
?>
Truncated output from ps command?
The solution lies in the way ps displays it's info
specifically the -w option which:
'uses 132 columns to display information,
instead of the default which is your window size.'....
somehow with fgets in php that results in 74 characters
regardless off the init length parameter
a bit of code:
<?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 =<>=
There is a simple way to start a process in the background but still find out what the process result is. I combined the information from some users below with some of my own coming up with the following:
<?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);
}
//
// Start the process in the background
//
$exe = "start /b ".$bat_filename;
if( pclose(popen($exe, 'r')) ) {
return true;
}
return false;
?>
In my case the file names of the .bat and .log files weren't always the same, so I needed a dynamic way to create the .bat file. The output from the php command is saved to the log file with the >> command. All prints and errors are stored there. At a later time you can open the log file and see what happened.
The below code works for both way processing ;) Have fun folks
<?php
system("mkfifo pipeout");
$pipe = popen("./nwserver -module Chapter1E > pipeout","w");
$pipeout = fopen("pipeout", "r");
while ($s = fgets($pipeout,1024)) {
echo $s;
}
?>
Note for Windows users using popen with start to run an external script without having php wait.
e.g.:
pclose( popen( 'start /b php someLongScript.php *> nul', 'rb' ) );
If start can't find the exe it will open a popup message and pclose hangs until the popup is closed.
Another workaround for using popen() with "w" mode so that the stdout of the command reaches the browser:
An easy solution is to have two php scripts; "real.php" with the popen($cmd, "w") command in it, the other being "wrapper.php", a one liner that simply invokes system("php real.php");
Invoking "wrapper.php" from the browser allows the popen($cmd,"w") in "real.php" to work as expected, such that stdout of $cmd reaches the browser. If you try to skip the wrapper and just run "real.php", stdout of $cmd is lost to /dev/null.
I should say, my host uses a modified form of safe mode, so I don't know if that might have caused a problem with "popen" as opposed to "proc_open". With safe mode enabled, all words following the initial command string are treated as a single argument. Thus, echo y | echo x becomes echo "y | echo x". [Because of this,] LinixDude010's srcipt did not work for me. Seems wrong to read and write with popen, according to the manual.
The script produced pgp text, but there was something wrong with the text and I could not decode it.
This replacement script, using proc_open, which can read and write, DOES work:
<?php
function pgp_encrypt($keyring_location, $public_key_id, $plain_text) {
$encrypted_text='';
$key_id = EscapeShellArg($public_key_id);
putenv("PGPPATH=$keyring_location");
// encrypt the message
$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)) {
// read from the pipe
$encrypted_text .= $s;
}
fclose($pipes[1]);
// optional:
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", "to@domain.com", "dummy text to be encrypted");
print nl2br($message);
?>
From the popen linux programmers manual:
"The command argument is a pointer to a null-terminated string containing a shell command line. This command is passed to /bin/sh using the -c flag."
Since php uses this popen function, you need to be sure /bin/sh exists. This file may not exist in chroot()ed environments.
I had all kinds of trouble encrypting a message with PGP, but I finanlly got it to work. The trick was to 'chmod o+r pubring.pkr' so that the apache server could read the public keys!!! Then, this function worked fine:
<?PHP
function pgp_encrypt($keyring_location, $public_key_id, $plain_text) {
$key_id = EscapeShellArg($public_key_id);
putenv("PGPPATH=$keyring_location");
// encrypt the message
$pipe = popen("pgpe -r $key_id -af", "r");
fwrite($pipe, $plain_text);
$encrypted_text = '';
while($s = fgets($pipe, 1024)) {
// read from the pipe
$encrypted_text .= $s;
}
pclose($pipe);
return $encrypted_text;
}
$message = pgp_encrypt("/home/username/.pgp", "to@domain.com", "dummy text to be encrypted");
print nl2br($message);
?>
If you are running in a chroot'ed environment on Debian "Squeeze", this command won't work; there is a problem with the kernel code that popen() eventually calls.
Note that pecl makes heavy use of this command, so if you are running in this environment you will need to install the pecl extension from source instead.
Here is a nice little script for monitoring your http access log.
<?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