很多情況下需要php調(diào)用其他程序如shell命令、shell腳本、可執(zhí)行程序等等,此時(shí)需要使用到諸如exec/system/popen/proc_open等函數(shù),每種函數(shù)有各自適合使用的場(chǎng)景以及需要注意的地方。

前提:PHP沒(méi)有運(yùn)行在安全模式
如果PHP運(yùn)行在安全模式下,那么在執(zhí)行外部命令、打開(kāi)文件、連接數(shù)據(jù)庫(kù)、基于HTTP的認(rèn)證這4個(gè)方面將會(huì)受到制約,可能在調(diào)用外部程序時(shí)無(wú)法獲取預(yù)期的結(jié)果,此時(shí)需要設(shè)置特定目錄,可以在php.ini中編輯safe_mode_exec_dir參數(shù)來(lái)指定。

1. exec
原型:string exec ( string command [, array &output [, int &return_var]] )
描述:返回值保存最后的輸出結(jié)果,而所有輸出結(jié)果將會(huì)保存到$output數(shù)組,$return_var用來(lái)保存命令執(zhí)行的狀態(tài)碼(用來(lái)檢測(cè)成功或失敗)。
例子:$ret = exec("ls -al", $output, $var);
注意:
A. 輸出結(jié)果會(huì)逐行追加到$output中,因此在調(diào)用exec之前需要unset($output),特別是循環(huán)調(diào)用的時(shí)候。
B. 如果想通過(guò)exec調(diào)用外部程序后馬上繼續(xù)執(zhí)行后續(xù)代碼,僅僅在命令里加"&"是不夠的,此時(shí)exec依然會(huì)等待命令執(zhí)行完畢;需要再將標(biāo)準(zhǔn)輸出做重定向才可以,例如:exec("ls -al >/dev/null &", $output, $var);
C. 要學(xué)會(huì)善用EscapeShellCmd()和EscapeShellArg()。函數(shù)EscapeShellCmd把一個(gè)字符串 中所有可能瞞過(guò)Shell而去執(zhí)行另外一個(gè)命令的字符轉(zhuǎn)義。這些字符在Shell中是有特殊含義的,象分號(hào)(|),重定向(>)和從文件讀入 (<)等。函數(shù)EscapeShellArg是用來(lái)處理命令的參數(shù)的。它在給定的字符串兩邊加上單引號(hào),并把字符串中的單引號(hào)轉(zhuǎn)義,這樣這個(gè)字符串 就可以安全地作為命令的參數(shù)。

2. system
原型:string system ( string command [, int &return_var] )
描述:執(zhí)行給定的命令,返回最后的輸出結(jié)果;第二個(gè)參數(shù)是可選的,用來(lái)得到命令執(zhí)行后的狀態(tài)碼。
例子:$ret = system("ls -al", $var);
注意:略。

3. passthru
原型:void passthru (string command [, int return_var])
描述:執(zhí)行給定的命令,但不返回任何輸出結(jié)果,而是直接輸出到顯示設(shè)備上;第二個(gè)參數(shù)可選,用來(lái)得到命令執(zhí)行后的狀態(tài)碼。
例子:passthru("ls -al", $var);
注意:略。

4. popen
原型:resource popen ( string command, string mode )
描述:打開(kāi)一個(gè)指向進(jìn)程的管道,該進(jìn)程由派生給定的 command 命令執(zhí)行而產(chǎn)生。 返回一個(gè)和 fopen() 所返回的相同的文件指針,只不過(guò)它是單向的(只能用于讀或?qū)懀┎⑶冶仨氂?pclose() 來(lái)關(guān)閉。此指針可以用于 fgets(),fgetss() 和 fwrite()。
例子:$fd = popen("command", 'r'); $ret = fgets($fd);
注意:只能打開(kāi)單向管道,不是'r'就是'w';并且需要使用pclose()來(lái)關(guān)閉。

5. proc_open
原型:resource proc_open ( string cmd, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]] )
描述:與popen類(lèi)似,但是可以提供雙向管道。具體的參數(shù)讀者可以自己翻閱資料,比如該博客:http://hi.baidu.com/alex_wang58/blog/item/a28657de16fec55195ee372a.html
注意:
A. 后面需要使用proc_close()關(guān)閉資源,并且如果是pipe類(lèi)型,需要用pclose()關(guān)閉句柄。
B. proc_open打開(kāi)的程序作為php的子進(jìn)程,php退出后該子進(jìn)程也會(huì)退出。
C. 筆者在使用的時(shí)候遇到獲取外部程序輸出阻塞的問(wèn)題,也就是在例子中的fgets($pipes[1])語(yǔ)句阻塞了,無(wú)法繼續(xù)進(jìn)行。經(jīng)過(guò)多方查證后發(fā)現(xiàn),問(wèn)題一般出在外部程序中,比如外部程序是C程序,使用fprintf(stdin, "**** \n");輸出結(jié)果,此時(shí)需要加上fflush(stdout);才行,否則輸出結(jié)果可能會(huì)暫留緩存中,無(wú)法真正輸出,而php也就無(wú)法獲取輸出了。
例子:
///< 打開(kāi)管道
$pwd = "*****";
$pipes = array();
$command = "*****";
$desc = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
$handle = proc_open($command, $desc, $pipes, $pwd);
if (!is_resource($handle)) {
    
fprintf(STDERR, "proc_open failed.\n");
    
exit(1);
}
///< 讀寫(xiě)
fwrite($pipes[0], "*****\n");
$ret = rtrim(fgets($pipes[1]), "\n");
///< 關(guān)閉管道
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($handle);