PHP Conference Japan 2024

exec

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

exec執行外部程式

描述

exec(字串 $command, 陣列 &$output = null, 整數 &$result_code = null): 字串|false

exec() 執行給定的 command

參數

command

要執行的指令。

output

如果存在 output 參數,則指定的陣列將會填入來自指令的每一行輸出。 結尾的空白字元,例如 \n,不會包含在此陣列中。 請注意,如果陣列已經包含某些元素,exec() 將會附加到陣列的結尾。 如果您不希望函式附加元素,請在將其傳遞給 exec() 之前,對陣列呼叫 unset()

result_code

如果 result_code 參數與 output 參數一起存在,則執行指令的返回狀態將會寫入此變數。

回傳值

來自指令結果的最後一行。 如果您需要執行指令,並讓指令的所有資料直接傳回而沒有任何干擾,請使用 passthru() 函式。

失敗時返回 false

若要取得執行指令的輸出,請務必設定並使用 output 參數。

錯誤/例外

如果 exec() 無法執行 command,則發出 E_WARNING

如果 command 為空或包含空位元組,則拋出 ValueError

變更記錄

版本 描述
8.0.0 如果 command 為空或包含空位元組,exec() 現在會拋出 ValueError。 之前它會發出 E_WARNING 並返回 false

範例

範例 #1 exec() 範例

<?php
// 輸出擁有正在執行的 php/httpd 程序的使用者名稱
// (在路徑中有 "whoami" 可執行檔的系統上)
$output=null;
$retval=null;
exec('whoami', $output, $retval);
echo
"Returned with status $retval and output:\n";
print_r($output);
?>

上面的範例會輸出類似下列的內容

Returned with status 0 and output:
Array
(
    [0] => cmb
)

注意事項

警告

當允許使用者提供的資料傳遞到此函式時,請使用 escapeshellarg()escapeshellcmd(),以確保使用者無法欺騙系統執行任意命令。

注意:

如果使用此函式啟動程式,為了使其繼續在背景中執行,程式的輸出必須重新導向到檔案或其他輸出串流。 如果沒有這樣做,將會導致 PHP 凍結,直到程式執行結束。

注意:

在 Windows 上,exec() 會先啟動 cmd.exe 來啟動指令。 如果您想要啟動外部程式而不啟動 cmd.exe,請使用設定 bypass_shell 選項的 proc_open()

參見

新增筆記

使用者貢獻的筆記 22 筆筆記

189
Arno van den Brink
16 年前
這會在背景中執行 $cmd (沒有 cmd 視窗),而 PHP 不會等待它完成,無論是 Windows 還是 Unix。

<?php
function execInBackground($cmd) {
if (
substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}
?>
84
dell_petter at hotmail dot com
15 年前
(這僅適用於 Linux 使用者)。

我們現在知道如何使用 & 運算子在 Linux 中 fork 程序。
透過使用命令:nohup MY_COMMAND > /dev/null 2>&1 & echo $!,我們可以傳回程序的 pid。

這個小類別是為了讓您可以追蹤您建立的程序 (意思是啟動/停止/狀態)。

您可以使用它來啟動程序或加入現有的 PID 程序。

<?php
// 您可以使用 status()、start() 和 stop()。請注意 start() 方法會自動呼叫一次。
$process = new Process('ls -al');

// 或者如果您有 pid,但這裡只會執行 status() 方法。
$process = new Process();
$process.setPid(my_pid);
?>

<?php
// 然後您可以啟動/停止/檢查工作的狀態。
$process.stop();
$process.start();
if (
$process.status()){
echo
"程序目前正在執行";
}else{
echo
"程序未執行。";
}
?>

<?php
/* 一個追蹤外部程序的好方法。
* 是否曾經想在 php 中執行一個程序,但仍然想對該程序有一定的控制?嗯...這是一種實現方式。
* @compability: 僅限 Linux。(Windows 不適用)。
* @author: Peec
*/
class Process{
private
$pid;
private
$command;

public function
__construct($cl=false){
if (
$cl != false){
$this->command = $cl;
$this->runCom();
}
}
private function
runCom(){
$command = 'nohup '.$this->command.' > /dev/null 2>&1 & echo $!';
exec($command ,$op);
$this->pid = (int)$op[0];
}

public function
setPid($pid){
$this->pid = $pid;
}

public function
getPid(){
return
$this->pid;
}

public function
status(){
$command = 'ps -p '.$this->pid;
exec($command,$op);
if (!isset(
$op[1]))return false;
else return
true;
}

public function
start(){
if (
$this->command != '')$this->runCom();
else return
true;
}

public function
stop(){
$command = 'kill '.$this->pid;
exec($command);
if (
$this->status() == false)return true;
else return
false;
}
}
?>
2
tsmtgdi at gmail dot com
1 年前
請注意,EXEC 會忽略 stderr!

所以像這樣的程式碼:

mkdir /impossible path

將會失敗,並且不會顯示任何內容,你必須使用

mkdir /impossible 2>&1

才能正確捕獲錯誤訊息

否則
如果是在 CLI 中執行,將只會印在終端機上,如果在瀏覽器中執行則會遺失。

我強烈建議將這些註解新增至主要描述中,而不是遺失在底部的註解中。
45
Simon
10 年前
無法讓你的 exec 執行的命令輸出顯示在 $output 陣列中嗎?
而是將其 echo 到你的 shell 中嗎?

在你的命令末尾附加 "2>&1",例如

exec("xmllint --noout ~/desktop/test.xml 2>&1", $retArr, $retVal);

將會使用預期的輸出填滿 $retArr 陣列;每個陣列索引鍵一行。
27
msheakoski @t yahoo d@t com
20 年前
我也曾努力讓程式在 Windows 背景中執行,同時腳本繼續執行。與其他解決方案不同,此方法允許你以最小化、最大化或完全不顯示視窗的方式啟動任何程式。llbra@phpbrasil 的解決方案確實有效,但當你真的希望工作隱藏執行時,有時會在桌面上產生不想要的視窗。

在背景中以最小化方式啟動 Notepad.exe

<?php
$WshShell
= new COM("WScript.Shell");
$oExec = $WshShell->Run("notepad.exe", 7, false);
?>

在背景中以不可見方式啟動 shell 命令
<?php
$WshShell
= new COM("WScript.Shell");
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false);
?>

最大化啟動 MSPaint 並等待你關閉它,然後再繼續腳本
<?php
$WshShell
= new COM("WScript.Shell");
$oExec = $WshShell->Run("mspaint.exe", 3, true);
?>

如需關於 Run() 方法的詳細資訊,請參閱
http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp
9
Farhad Malekpour
17 年前
如果你嘗試在使用了訊號 SIGCHLD 的腳本中使用 exec (例如 pcntl_signal(SIGCHLD,'sigHandler');),它將會傳回 -1 作為命令的結束代碼 (雖然輸出是正確的!)。若要解決此問題,請移除訊號處理常式,然後在 exec 後重新新增它。程式碼看起來會像這樣

...
pcntl_signal(SIGCHLD, 'sigHandler');
...
...
(更多程式碼、函式、類別等等)
...
...
// 現在透過 exec 執行命令
// 清除訊號
pcntl_signal(SIGCHLD, SIG_DFL);
// 執行命令
exec('mycommand',$output,$retval);
// 將訊號設定回我們的處理常式
pcntl_signal(SIGCHLD, 'sigHandler');
// 在此時,我們有 $retval 的正確值。

相同的解決方案也適用於 system 和 passthru。
4
Paul Sommer
8 年前
我嘗試在 Windows 下於背景執行命令。
在為這些半成品範例奮鬥了數小時之後,我想分享我發現有效的語法 (至少適用於 Windows)。這沒有在 Linux 下測試,因為有更優雅的方式來產生程序。

基於 Arno van den Brink 的函式。

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

這在例如以下情況下可以完美運作
<?php
execInBackground
('del c:\tmp\*.*')
?>

但以下情況不起作用
<?php
execInBackground
('\"c:\path with spaces\my program.exe\"')
?>

為什麼?
當 Windows 看到引號 (\") 時,會認為這是視窗標題,而不是命令。
因此,當你的命令需要引號時,你必須先提供視窗名稱,像這樣
execInBackground("\"title\"" "\"c:\path with spaces\my program.exe\")

視窗標題必須要有引號。否則 Windows 會認為這是程式名稱。

很奇怪,但是「嘿!這是 Windows!」:)
2
Bob-PHP at HamsterRepublic dot com
19 年前
exec 會從命令的輸出中刪除尾隨的空格。這使得無法捕獲重要的空格。例如,假設程式輸出一列列以 Tab 分隔的文字,而最後一列在某些行包含空白欄位。尾隨的 Tab 很重要,但會被丟棄。

如果你需要保留尾隨的空格,你必須改用 popen()。
2
elwiz at 3e dot pl
14 年前
在 Windows-Apache-PHP 伺服器上,同時多次使用 exec 命令會出現問題。如果同一個使用者同時多次載入使用 exec 命令的腳本,伺服器將會凍結。
在我的情況下,使用 exec 命令的 PHP 腳本被用作圖像標籤的來源。一個 HTML 中有多個影像會導致伺服器停止。
此問題的描述在此處(http://bugs.php.net/bug.php?id=44942),同時也提供了解決方案 - 在執行 exec 命令之前先停止 session,執行後再重新啟動。

<?php

session_write_close
();
exec($cmd);
session_start();

?>
2
Hypolite Petovan
4 年前
在 Unix 系統上,若要在背景執行命令 $cmd,唯一允許的標準輸出重新導向語法是 "> /path/to/file &"。其他任何有效的標準輸出重新導向語法都無法讓命令在背景執行。

<?php
// 以下命令將在背景執行
exec($cmd . " > /path/to/file &");

// 以下所有命令將不會在背景執行
exec($cmd . " >> /path/to/file &");
exec($cmd . " &> /path/to/file &");
exec($cmd . " &>> /path/to/file &");
exec($cmd . " 2>&1 > /path/to/file &");
exec($cmd . " 2>&1 >> /path/to/file &");
?>
2
hans at internit dot NO_SPAM dot com
22 年前
根據我四處詢問所收集到的資訊,似乎沒有辦法使用 exec 函式將 Perl 陣列傳回 PHP 腳本。

建議的方式是在你的 Perl 腳本的結尾印出你的 Perl 陣列變數,然後從 exec 函式返回的陣列中抓取每個陣列成員。如果你要傳遞多個陣列,或者你需要追蹤陣列的鍵和值,那麼當你在你的 Perl 腳本的結尾印出每個陣列或雜湊變數時,你應該將值與鍵和陣列名稱用底線連接起來,例如:

foreach (@array) print "(陣列名稱)_(成員鍵)_($_)" ;

然後你只需要迭代 exec 函式返回的陣列,並沿著底線分割每個變數。

我特別感謝 Marat 提供這些知識。希望這對其他正在尋找類似答案的人有所幫助!
2
php dot reg at kjpetrie dot co dot uk
2 年前
如果你想在背景執行命令,並且也想使用建議的 escapeshellcmd(),請確保你在括號外進行重新導向!這些是你希望 shell 解釋的東西。

例如:exec(escapeshellcmd('我的命令和參數').' > /dev/null &');
2
juan at laluca dot com
13 年前
我嘗試通過執行 cacls 並在 PHP 中解析它,從遠端電腦取得存取權限清單,所有操作都在使用 Apache 的 Windows 環境中進行。首先我發現了 Windows SysInternals 的 psexec.exe。

但是使用以下程式碼,我沒有得到任何東西,它卡住了,儘管從命令列中它運行良好

<?php exec ('c:\\WINDOWS\\system32\\psexec.exe \\192.168.1.224 -u myuser -p mypassword -accepteula cacls c:\\documents\\RRHH && exit', $arrACL ); ?>

為了讓它工作,我只是按照以下步驟操作
- 執行 services.msc 並找到 apache 服務(在我的情況下是 wampapache)
- 按右鍵 > 登入標籤,並將「本機系統帳戶」更改為建立的使用者帳戶,輸入使用者名稱和密碼並重新啟動服務。

(我將此使用者添加到 administrators 群組以避免權限問題,但不建議這樣做...)

它奏效了!它可能也適用於 IIS,所以如果你的問題相同,請嘗試一下...

希望這能幫助到某些人,並為我的英語感到抱歉
0
mamedul.github.io
1 年前
使用 PHP 執行命令的跨函式解決方案 -

function php_exec( $cmd ){

if( function_exists('exec') ){
$output = array();
$return_var = 0;
exec($cmd, $output, $return_var);
return implode( " ", array_values($output) );
}else if( function_exists('shell_exec') ){
return shell_exec($cmd);
}else if( function_exists('system') ){
$return_var = 0;
return system($cmd, $return_var);
}else if( function_exists('passthru') ){
$return_var = 0;
ob_start();
passthru($cmd, $return_var);
$output = ob_get_contents();
ob_end_clean(); // 使用這個而不是 ob_flush()
return $output;
}else if( function_exists('proc_open') ){
$proc=proc_open($cmd,
array(
array("pipe","r"),
array("pipe","w"),
array("pipe","w")
),
$pipes);
return stream_get_contents($pipes[1]);
}else{
return "@PHP_COMMAND_NOT_SUPPORT";
}

}
0
no at mail dot com
1 年前
<此註解複製自 system 頁面>
這是給 WINDOWS 使用者的。我正在運行 apache,我已經嘗試了幾個小時來擷取命令的輸出。

我嘗試了這裡寫的所有方法,然後繼續在網路上搜尋,但沒有任何進展。命令的輸出從未被擷取。我得到的只是一個空陣列。

最後,我在某個很棒的人的部落格中找到了一個解決我問題的評論。

在命令名稱中加入字串 ' 2>&1' 最終返回了輸出!!這在 PHP 的 exec() 和 system() 中都有效,因為它使用串流重新導向將輸出重新導向到正確的位置!
<?php
exec
("你的命令名稱 2>&1", $output);
var_dump($output);
?>
2
Martin Lakes
13 年前
花了一段時間才弄清楚我接下來要發布的這行程式碼。如果你想在背景執行命令,而無需腳本等待結果,你可以執行以下操作

<?php
passthru
("/usr/bin/php /path/to/script.php ".$argv_parameter." >> /path/to/log_file.log 2>&1 &");
?>

這裡有幾件事很重要。

首先:放入 php 二進制檔案的完整路徑,因為此命令將在 apache 使用者下運行,並且你可能沒有在該使用者中設定像 php 這樣的命令別名。

第二:請注意命令字串結尾的 2 個事項:'2>&1' 和 '&'。 '2>&1' 用於將錯誤重新導向到標準 IO。而最重要的是命令字串結尾的 '&',它會告訴終端機不要等待回應。

第三:確保你對 'log_file.log' 檔案擁有 777 權限

享受吧!
0
krjdev at gmail dot com
3 年前
給 OpenBSD 使用者的注意事項

在 OpenBSD 的預設配置中,PHP 會在 chroot 中執行。因此,exec() 命令將無法運作。你會得到 127(找不到命令)的結果代碼。原因是在 chroot 中缺少 shell (/bin/sh),但 exec() 命令需要 shell。

一個快速而簡陋的解決方案是將 /bin/sh(它是靜態連結的)複製到 chroot 目錄中
<?php
$ cp /bin/sh /var/www/bin
?>

(我在 OpenBSD 7.0 上使用 PHP 8.0.11 時注意到這一點。)
1
alvaro at demogracia dot com
13 年前
在 Windows 中,exec() 會對 "cmd /c your_command" 發出內部呼叫。這表示你的命令必須遵循 cmd.exe 強加的規則,其中包括在整個命令周圍加上一組額外的引號。

- http://ss64.com/nt/cmd.html

目前的 PHP 版本會考慮到這一點並自動添加引號,但舊版本則不會。

顯然,此變更是在 PHP/5.3.0 中進行的,但並未向後移植到 5.2.x,因為這是一個不相容的變更。總結如下

- 在 PHP/5.2 和更舊的版本中,你必須將完整命令加上參數都用雙引號括起來
- 在 PHP/5.3 和更新的版本中,你不需要這樣做(如果你這樣做,你的腳本將會壞掉)

如果你對內部機制感興趣,這是原始碼

sprintf(cmd, "%s /c \"%s\"", TWG(comspec), command);

可以在 http://svn.php.net/viewvc/ 找到(請尋找 php/php-src/trunk/TSRM/tsrm_win32.c,評論系統不允許直接連結)。
0
bahri at bahri dot info
15 年前
[danbrown AT php DOT net 的註解:以下是一個 Linux 腳本,此註解的貢獻者建議將其放置在名為 'pstools.inc.php' 的檔案中,以執行進程、檢查進程是否存在,以及依 ID 終止進程。靈感來自 https://php.dev.org.tw/exec#59428 的 Windows 版本]


<?php
function PsExecute($command, $timeout = 60, $sleep = 2) {
// 首先,執行程序,取得程序 ID

$pid = PsExec($command);

if(
$pid === false )
return
false;

$cur = 0;
// 第二,迴圈檢查 $timeout 秒,確認程序是否仍在執行
while( $cur < $timeout ) {
sleep($sleep);
$cur += $sleep;
// 如果程序已停止執行,回傳 true;

echo "\n ---- $cur ------ \n";

if( !
PsExists($pid) )
return
true; // 程序必定已結束,成功!
}

// 如果程序在逾時後仍在執行,終止程序並回傳 false
PsKill($pid);
return
false;
}

function
PsExec($commandJob) {

$command = $commandJob.' > /dev/null 2>&1 & echo $!';
exec($command ,$op);
$pid = (int)$op[0];

if(
$pid!="") return $pid;

return
false;
}

function
PsExists($pid) {

exec("ps ax | grep $pid 2>&1", $output);

while( list(,
$row) = each($output) ) {

$row_array = explode(" ", $row);
$check_pid = $row_array[0];

if(
$pid == $check_pid) {
return
true;
}

}

return
false;
}

function
PsKill($pid) {
exec("kill -9 $pid", $output);
}
?>
0
dr_jones153 at hotmail dot com
16 年前
如果啟用了 SAFE_MODE,並且您嘗試在背景執行腳本,方法是在命令列後附加 "> /dev/null 2> /dev/null & echo $!",瀏覽器將會掛起直到腳本完成。

我的解決方案

建立一個 shell 腳本 (例如 runscript.sh),其中包含您嘗試在背景執行的腳本的執行行。
runscript.sh 是透過 exec() 呼叫執行,不帶有重新導向字串,該字串現在放置在 runscript.sh 中。

runscript.sh 將幾乎立即返回,因為原始腳本的輸出已重新導向,因此不會使您的瀏覽器掛起,並且腳本會在背景中順利執行。
-1
ivk
3 年前
result_code -1 可能表示「已達到檔案描述符的最大數量」。請檢查 count(get_resources('stream')) 和 ulimit -Sn
-1
layton at layton dot tk
19 年前
這是我第二次遇到這個問題,我想其他人可能也會覺得這個筆記有用。

我正在建立一個長時間執行的 exec 程序,我可以在 2 小時後透過頁面提交來存取它。問題是這樣,我第一次存取頁面時,一切都像應該的那樣工作。第二次,網頁瀏覽器等待又等待,永遠沒有收到任何訊息 -- CPU 時間不受影響,因此顯然有些東西被封鎖了。

實際發生的情況是,所有開啟的檔案都會被複製到 exec 程序中 -- 包括網路連線。因此,第二次我嘗試存取網頁時,我被賦予了舊的 http 網路連線,現在該連線被忽略了。

解決方案是從 3 開始掃描所有檔案控制代碼,並關閉它們。請記住,控制代碼 0、1 和 2 是標準輸入、標準輸出和標準錯誤。
To Top