2024 年 PHP Conference Japan

ssh2_exec

(PECL ssh2 >= 0.9.0)

ssh2_exec在遠端伺服器上執行命令

說明

ssh2_exec(
    資源 $session,
    字串 $command,
    字串 $pty = ?,
    陣列 $env = ?,
    整數 (int) $width = 80,
    整數 (int) $height = 25,
    整數 (int) $width_height_type = SSH2_TERM_UNIT_CHARS
): 資源 (resource)|false

在遠端執行命令並為其分配通道。

參數

session

一個 SSH 連線鏈結識別碼,由呼叫 ssh2_connect() 取得。

command

pty

env

env 可以作為名稱/值對的關聯陣列傳遞,以在目標環境中設定。

width

虛擬終端機的寬度。

height

虛擬終端機的高度。

width_height_type

width_height_type 應該是 SSH2_TERM_UNIT_CHARSSSH2_TERM_UNIT_PIXELS 其中之一。

回傳值

成功時返回一個串流,失敗時返回 false

範例

範例 #1 執行命令

<?php
$connection
= ssh2_connect('shell.example.com', 22);
ssh2_auth_password($connection, 'username', 'password');

$stream = ssh2_exec($connection, '/usr/local/bin/php -i');
?>

參見

新增註釋

使用者貢獻的註釋 17 則註釋

magicaltux at php dot net
15 年前
參數 "pty" 沒有說明文件。

如果您想模擬 pty,您應該傳遞一個 pty 模擬名稱("vt102"、"ansi" 等),如果不模擬則傳遞 NULL。

傳遞 false 會將 false 轉換為字串,並會配置一個 "" 終端機。
christopher dot millward at gmail dot com
14 年前
似乎沒有辦法同時從 stderr 和 stdout 串流取得資料,或者至少我還沒找到。

以下程式碼*應該*可以取得寫入 stderr 的錯誤訊息,但如果先執行 stdout 的 stream_get_contents() 呼叫,則後續 stderr 的呼叫將不會返回任何內容。

如果語句的順序相反,stderr 的呼叫將返回任何錯誤,而 stdout 的呼叫將不返回任何內容。

<?php
// 執行可能會寫入 stderr 的指令(除非您有名為 /hom 的資料夾)
$stream = ssh2_exec($connection, "cd /hom");
$errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);

// 啟用兩個串流的阻塞
stream_set_blocking($errorStream, true);
stream_set_blocking($stream, true);

// 以下兩個指令中,先列出的那個將會收到其相應的輸出。第二個指令則不會收到任何內容。
echo "輸出: " . stream_get_contents($stream);
echo
"錯誤: " . stream_get_contents($errorStream);

// 關閉串流
fclose($errorStream);
fclose($stream);

?>

我最初的懷疑是 a) 我做錯了什麼,或者 b) 兩個串流的阻塞特性意味著當第一個串流收到資料並返回時,第二個串流的緩衝區已經清空。

我已經在停用阻塞的情況下進行了初步測試,但也沒有任何運氣。
Rainer Perske
4 年前
即使兩個串流都包含數 GB 的資料,也可以使用以下函式讀取 stdout 和 stderr。它解決了其他評論中提到的多數問題,例如阻塞、進一步使用現有連線等。

function ssh2_run($ssh2,$cmd,&$out=null,&$err=null){
$result=false;
$out='';
$err='';
$sshout=ssh2_exec($ssh2,$cmd);
if($sshout){
$ssherr=ssh2_fetch_stream($sshout,SSH2_STREAM_STDERR);
if($ssherr){
# 我們無法將 stream_select() 與 SSH2 串流一起使用
# 因此使用非阻塞的 stream_get_contents() 和 usleep()
if(stream_set_blocking($sshout,false) and
stream_set_blocking($ssherr,false)
){
$result=true;
# 循環直到 stdout 和 stderr 的輸出結束
$wait=0;
while(!feof($sshout) or !feof($ssherr)){
# 僅在未讀取任何資料後才休眠
if($wait)usleep($wait);
$wait=50000; # 1/20 秒
if(!feof($sshout)){
$one=stream_get_contents($sshout);
if($one===false){ $result=false; break; }
if($one!=''){ $out.=$one; $wait=0; }
}
if(!feof($ssherr)){
$one=stream_get_contents($ssherr);
if($one===false){ $result=false; break; }
如果 ($one != '') { $err .= $one; $wait = 0; }
}
}
}
# 我們需要等待指令執行完畢
stream_set_blocking($sshout, true);
stream_set_blocking($ssherr, true);
# 這些將不會取得任何輸出
stream_get_contents($sshout);
stream_get_contents($ssherr);
fclose($ssherr);
}
fclose($sshout);
}
return $result;
}
edoceo at gmail dot com
8 年前
以下是我用於執行 ssh2_exec 長時間運行腳本的程式碼,基於其他範例做了擴充。它會等待一段時間,並檢查兩個串流是否都已完成。不使用阻塞模式,並且會從 STDOUT 和 STDERR 取得資料。

<?php
$stdout
= ssh2_exec($ssh, $cmd);
$stderr = ssh2_fetch_stream($stdout, SSH2_STREAM_STDERR);
if (!empty(
$stdout)) {

$t0 = time();
$err_buf = null;
$out_buf = null;

// 嘗試 30 秒
do {

$err_buf .= fread($stderr, 4096);
$out_buf .= fread($stdout, 4096);

$done = 0;
if (
feof($stderr)) {
$done++;
}
if (
feof($stdout)) {
$done++;
}

$t1 = time();
$span = $t1 - $t0;

// 資訊提示
echo "while (($tD < 20) && ($done < 2));\n";

// 在此等待,避免迴圈過於頻繁
sleep(1);

} while ((
$span < 30) && ($done < 2));

echo
"STDERR:\n$err_buf\n";
echo
"STDOUT:\n$out_buf\n";

echo
"完成\n";

} else {
echo
"Shell 執行失敗\n";
}
?>

根據需要調整逾時時間。
jaimie at seoegghead dot com
15 年前
我相信大多數人遇到的問題是對阻塞 *真正* 含義的誤解。

阻塞意味著從 *串流* 讀取將會等待,直到有資料為止。不一定是來自應用程式的 *所有* 資料,而是 *一些* 資料。因此,如果您執行的指令沒有寫入 stdout,或者寫入了大量資料,這對您一點幫助也沒有。

所以有兩個問題

1. 如果您需要透過 ssh2_exec 知道一個靜默程式已完成,您需要自行發送訊號。ssh2_exec *不會* 阻塞執行,直到命令執行完成。而且兩個連續的 ssh2_execs 可能會非同步執行。您也可以透過 ssh2_shell 登入 shell 並解析到下一個提示符號 -- 但這有點殺雞用牛刀。您也可以在命令的末尾添加某種標記,例如 echo "@,",然後阻塞讀取直到看到 "@"。確保 "@" 不會出現在輸出中,或者如果您無法做到這一點,請透過某種編碼機制對輸出進行轉義。

2. 如果程式執行時間較長,您也會遇到相同的問題。您需要持續讀取直到完成。所以您需要像上面一樣的標記值。

3. 在這裡使用 Sleep() 並不是一個好主意。命令很少會花費相同的時間執行兩次。如果您只做 *一件* 事情並且可以等待 5 秒鐘,那還可以。但如果您在迴圈中執行某事,那就行不通了。

希望這個有幫助!
gakksimian at yahoo dot com
19 年前
執行遠端 Windows 命令...
經過一番努力後,我想我應該提供一些可能對其他人有幫助的建議

1. 根據手冊,使用 'ssh2_fetch_stream' 開啟 stderr 串流。
2. Windows shell 命令需要 'cmd /C [command]' 才能執行。(我之前忘記了這一點)

<?php
// 程式碼片段
$stdout_stream = ssh2_exec($connection, 'cmd /C dir');
$stderr_stream = ssh2_fetch_stream($stdout_stream, SSH2_STREAM_STDERR);
?>

直到我看到 stderr 回應 'dir: not found',我才意識到需要 'cmd /C'。
Betsy Gamrat
17 年前
最好也使用 register_shutdown_function 在執行後清除金鑰。
lko at netuse dot de
16 年前
如果您正在使用 exec 函式,並且輸出超過 1000 行時遇到問題
您應該使用

<?php
stream_set_blocking
($stream, true);
while(
$line = fgets($stream)) {
flush();
echo
$line."<br />";
}
?>

而不是

<?php
stream_set_blocking
($stream, true);
echo
stream_get_contents($stream);
?>
mlocati at gmail dot com
1 年前
自 SSH2 擴充套件 0.13 版起,可以使用 stream_get_meta_data() 擷取命令的退出狀態。

例如:

<?php
$channel
= ssh2_exec($connection, $command);

$metadata = stream_get_meta_data($channel);

$exitCode = $metadata['exit_status'];

?>

取得退出代碼非常有用,因為通常情況下,命令成功返回 0,錯誤則返回其他值。
yairl at savion dot huji dot ac dot il
19 年前
如果您想在同一個程序中透過 ssh2 執行多個 exec 命令,例如使用相同的變數 $stream 並讀取每個命令的輸出,則必須在每個命令之後關閉串流,否則您將無法讀取下一個命令的輸出。

<?php
$stream
=ssh2_exec($conn_id,"/usr/bin/ls .");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);

if(
ereg("public_html",$cmd)){
// ...........................................
}

$stream=ssh2_exec($conn_id,"/usr/bin/ls public_html");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);

if(
ereg("images",$cmd)){
// ..........................................
}


$stream=ssh2_exec($conn_id,"/usr/bin/ls public_html/images");
stream_set_blocking( $stream, true );
$cmd=fread($stream,4096);
fclose($stream);

if(
ereg("blabla",$cmd)){
// ..........................................
}
?>
tabber dot watts at gmail dot com
19 年前
這是我找到的自動執行多個指令或執行時間可能超過預期指令的最佳方法。注意:這假設輸出中沒有文字 '[end]',否則函式會提前結束。希望這能幫助大家。

<?php
$ip
= 'ip_address';
$user = 'username';
$pass = 'password';

$connection = ssh2_connect($ip);
ssh2_auth_password($connection,$user,$pass);
$shell = ssh2_shell($connection,"bash");

//訣竅在於起始和結束的 echo 指令,這可以在 *nix 和 Windows 系統中執行。
//如果在 Windows 系統上,請在 $cmd 的開頭添加 'cmd /C'。
$cmd = "echo '[start]';你的指令寫在這裡;echo '[end]'";
$output = user_exec($shell,$cmd);

fclose($shell);

function
user_exec($shell,$cmd) {
fwrite($shell,$cmd . "\n");
$output = "";
$start = false;
$start_time = time();
$max_time = 2; //時間,以秒為單位
while(((time()-$start_time) < $max_time)) {
$line = fgets($shell);
if(!
strstr($line,$cmd)) {
if(
preg_match('/\[start\]/',$line)) {
$start = true;
}elseif(
preg_match('/\[end\]/',$line)) {
return
$output;
}elseif(
$start){
$output[] = $line;
}
}
}
}

?>

[由 danbrown AT php DOT net 編輯:包含由 (jschwepp AT gmail DOT com) 於 2010 年 2 月 17 日提供的錯誤修正,以修正函式名稱中的拼寫錯誤。]
dosentnd at gmail dot com
4 年前
執行多個指令時會顯示此警告,並且第二個(以及任何後續)指令將不會被執行
- PHP 警告:ssh2_exec():無法從遠端主機請求通道,位於 ...

使用 fclose($stream) 的解決方案無效,但以下這個簡單的解決方案對我來說有效

<?php

$connection
= ssh2_connect('SOME SSH IP ADDRESS', 22);
ssh2_auth_password($connection, 'username', 'passwortd');

//這些指令適用於某些 HUAWEI 企業路由器

$stream = ssh2_exec( $connection, "screen-length 0 temporary\ndisplay isis name-table" );
stream_set_blocking( $stream, true );
$stream_out = ssh2_fetch_stream( $stream, SSH2_STREAM_STDIO );
echo
stream_get_contents($stream_out);

?>

第一個指令是:screen-length 0 temporary
第二個指令是:display isis name-table
它們在同一行中用 \n 執行。
您必須稍後將這些指令的輸出分開。

看起來 ssh2_exec 會在您連線的階段配置執行串流,要釋放它,您必須使用 ssh2_disconnect。
通常您可以執行 ssh2_exec 一個指令然後斷線,再連線並執行下一個 ssh2_exec 指令。 這是我見過的一些其他 php 函式庫的運作方式。 但有時某些變數與階段相關,當您斷線時,變數會重置。
Betsy Gamrat
17 年前
如果 ssh2_exec 執行需要一段時間,並且您需要一個握手信號,則可以使用檔案。 在這種情況下,$flag 命名一個在 ssh 指令碼完成時寫入的握手檔案。

<?php
$stream
= ssh2_exec($connection, $command . " $public_key $private_key;");
$i=0;
while (!
file_exists ($flag) && ($i < MAX_TIME))
{
sleep(1);
$i++;
}
$ret_val=($stream!=FALSE);
?>

這是正在執行的 bash 指令碼的摘錄。 請務必允許網路伺服器具有讀取指令碼寫入的檔案的權限。

echo 0 > $OUTFILE
chmod 666 $OUTFILE

另一點需要注意:您可以透過用「;」(分號)分隔多個指令來放入多個指令。
Jim
13 年前
需要注意的是,並非所有允許 SFTP 的主機都能正常運作。 我們拒絕 shell 的訪問權限,但允許使用 sftp-internal 子系統進行 SFTP。

當嘗試在此配置下使用 ssh2_exec() 並將 stream_set_blocking 設定為 true 時,指令碼將無限期掛起。 當 stream_set_blocking 設定為 false 時,將不會返回任何內容。

即使您可以使用 SFTP 傳輸檔案,也請務必與您的主機確認您是否具有 shell 的訪問權限。
noreply at voicemeup dot com
11 年前
*** 重要 ***
如果您在 PHP 5.2.X 版本中遇到 STDERR 的問題,請務必更新到 5.2.17 並從 PECL 安裝最新的 ssh2 擴充套件。
gwinans at gmail dot com
14 年前
很奇怪,我注意到如果我嘗試執行任何指令,什麼事都沒發生。我的意思是,不是「沒有回傳任何內容!」,而是「我要求執行的指令根本沒有執行!」。

除非遵循以下模式

<?php
$stream
= ssh2_exec($connection, "touch ~/some_fake_file", 'xterm');
stream_get_contents($stream);
?>

手冊中沒有任何地方暗示需要使用 stream_get_contents() 才能實際執行指令,但在這種情況下...似乎就是這樣。因此,如果您在執行指令時遇到問題,請嘗試使用 stream_get_contents()。
Jon-Eirik Pettersen
17 年前
如果串流未設定為阻塞模式,則指令可能在函式返回之前尚未完成。您可能必須將串流設定為阻塞模式才能從指令中取得任何輸出。

<?php
// [開啟連線]
// ...

$stream = ssh2_exec($connection, 'ps ax');
stream_set_blocking($stream, true);

// 如果未讀取到串流結尾,指令可能無法正常完成
$output = stream_get_contents($stream);

// ...
// [關閉串流和連線]
?>
To Top