PHP Conference Japan 2024

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 年前
作為 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);
?>

這樣我的主腳本就能繼續執行,而無需暫停,同時小腳本會在載入較大的檔案時暫停。
8
anonymous at anon dot com
13 年前
如果在 Windows 上,你需要啟動一個需要管理員權限的批次檔,那麼你可以建立一個該批次檔的捷徑,點擊內容,在其中一個內容頁面上勾選「以管理員身分執行」,然後雙擊該捷徑一次(以初始化「以管理員身分執行」的設定)。

使用 popen("/path/to/shortcut.lnk") 將會以管理員權限執行你的批次檔。

這對於當你想使用 CLI 的 PHP 來執行一些長時間運行的任務,而該 PHP-CLI 需要使用 sessions 時非常方便。
5
rockytriton
17 年前
請注意,當在 Windows 中使用批次檔時,你必須在批次檔的結尾加上 "exit",否則每次執行頁面時,你的處理程序清單中都會卡住一個新的 cmd.exe。
1
atampone at NOSPAMFORME dot trdsupra dot com
19 年前
如果你想在 Windows 下 fork 一個處理程序,這是要使用的函式。我建立了一個名為 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(或您正在執行的任何網頁伺服器)的相同使用者帳戶下執行,因此請確保它具有執行您需要執行的任何操作的權限。此外,請確保批次檔具有足夠的 %n 以便傳遞您可能需要傳遞的所有命令列變數。

特別感謝 devshed 論壇的 kicken 提出這個想法。
2
Marbug at 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))
{
// send the current file part to the browser
print fread($fp, 1024);
// flush the content to the browser
flush();
}
fclose($fp);
}
?>
1
rjl at xs4all dot nl
19 年前
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);
}

//
// Start the process in the background
//
$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
3 年前
Windows 使用者在使用 popen 和 start 來執行外部腳本,而無需讓 PHP 等待時請注意。

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

如果 start 找不到 exe,它會開啟一個快顯訊息,而 pclose 會掛起直到關閉快顯視窗。
1
erco at seriss dot com
6 年前
另一個針對在 "w" 模式下使用 popen(),以使命令的 stdout 傳送到瀏覽器的變通方法

一個簡單的解決方案是使用兩個 PHP 腳本;一個是包含 popen($cmd, "w") 命令的 "real.php",另一個是 "wrapper.php",一個簡單調用 system("php real.php") 的單行腳本。

從瀏覽器調用 "wrapper.php" 允許 "real.php" 中的 popen($cmd,"w") 如預期般工作,使得 $cmd 的 stdout 傳送到瀏覽器。如果你嘗試跳過 wrapper 直接執行 "real.php",$cmd 的 stdout 會遺失到 /dev/null。
0
PGP Dude
19 年前
我應該說,我的主機使用修改過的 Safe Mode,所以我不知道這是否會導致 "popen" 而不是 "proc_open" 的問題。在啟用 Safe Mode 的情況下,初始命令字串之後的所有單字都會被視為單一參數。因此,`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>錯誤:$s</p>\n";
}
fclose($pipes[2]);
}
return
$encrypted_text;
}

$message = pgp_encrypt("/home/username/.pgp", "to@domain.com", "要加密的虛擬文字");
print
nl2br($message);

?>
0
http://vmlinuz.nl/about/contact/
22 年前
來自 popen Linux 程式設計師手冊

「command 參數是指向包含 shell 命令行的 null 終止字串的指標。此命令會使用 -c 旗標傳遞至 /bin/sh。」

由於 PHP 使用此 popen 函數,您需要確保 /bin/sh 存在。此檔案可能不存在於 chroot() 環境中。
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", "to@domain.com", "要加密的虛擬文字");
print
nl2br($message);

?>
-1
mrjake2
14 年前
如果您在 Debian "Squeeze" 上於 chroot 環境中執行,此命令將無法運作;最終 popen() 呼叫的核心程式碼存在問題。

請注意,pecl 大量使用此命令,因此如果您在此環境中執行,則需要從原始碼安裝 pecl 擴充功能。
-2
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
-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 年前
我注意到上面的一些範例似乎提倡在沒有雙向 popen (在某些作業系統上) 的情況下,透過管道 shell escape 將未加密的資料傳遞給 gpg。

我採用的方法類似於

<?php
$prefix
= 'example';
$command = '/usr/local/bin/gpg --encrypt --armor --no-tty --batch --no-secmem-warning --recipient "joe.soap@example.com"';
$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() 之前先對 handle 執行讀取操作,即使命令沒有輸出任何內容!所以...

<?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 <another@fake.email>';

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 消耗了一個 GB 的交換空間。

有問題的程式碼 - 請勿複製

<?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
匿名使用者
22 年前
這裡有一個在 PHP 中沒有雙向管道的解決方案。

如果你有雙向管道支援,則不必理會這個方法。

這裡的技巧是在命令行上將輸入發送到目標應用程式。特別是,我想要在不使用臨時文件或具名管道的情況下使用 openssl。這個解決方案也應該是線程/進程安全的。

這在 Linux(RedHat 7)上可以運作。

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

# 範例:
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