PHP Conference Japan 2024

shell_exec

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

shell_exec透過 shell 執行命令並以字串傳回完整輸出

描述

shell_exec(字串 $command): 字串|false|null

此函式與反引號運算子相同。

注意:

在 Windows 上,底層的管道會以文字模式開啟,這可能會導致此函式在二進位輸出時失敗。在這種情況下,請考慮改用 popen()

參數

command

將要執行的命令。

回傳值

包含執行命令輸出的字串,如果無法建立管道則為 false,若發生錯誤或命令沒有產生任何輸出則為 null

注意:

當發生錯誤或程式沒有產生任何輸出時,此函式都會傳回 null。 無法使用此函式偵測執行失敗。當需要存取程式的退出代碼時,應使用 exec()

錯誤/例外

當無法建立管道時,會產生 E_WARNING 級別的錯誤。

範例

範例 #1 shell_exec() 範例

<?php
$output
= shell_exec('ls -lart');
echo
"<pre>$output</pre>";
?>

參見

新增註解

使用者貢獻的註解 35 則註解

trev at dedicate.co.uk
13 年前
如果您嘗試在 shell_exec 中執行 "gunzip -t" 之類的命令並得到空白結果,您可能需要在命令結尾新增 2>&1,例如

不一定總是有效
echo shell_exec("gunzip -c -t $path_to_backup_file");

應該有效
echo shell_exec("gunzip -c -t $path_to_backup_file 2>&1");

在上面的範例中,gunzip 輸出開頭的換行符號似乎會阻止 shell_exec 列印任何其他內容。希望這能為其他人節省一兩個小時。
alexandre dot schmidt at gmail dot com
8 年前
要在背景執行命令,輸出必須重新導向至 /dev/null。這寫在 exec() 手冊頁面中。在某些情況下,您需要將輸出記錄在其他地方。像這樣將輸出重新導向到檔案對我來說沒有用

<?php
# 這不起作用!
shell_exec("my_script.sh 2>&1 >> /tmp/mylog &");
?>

使用上述命令仍然會掛起網頁瀏覽器請求。

似乎您必須在命令列中準確加入 "/dev/null"。例如,這有效

<?php
# 有效,但輸出遺失
shell_exec("my_script.sh 2>/dev/null >/dev/null &");
?>

但我想要輸出,所以我使用了這個

<?php
shell_exec
("my_script.sh 2>&1 | tee -a /tmp/mylog 2>/dev/null >/dev/null &");
?>

希望這能幫助其他人。
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";
}

}
shell master
1 年前
您可以使用單行程式碼來列印 shell script 的輸出,如下所示

<?= shell_exec("/proof.sh"); ?>
Rgemini
15 年前
在 Windows 下使用 shell-exec 時,處理捕獲 stderr 輸出問題的簡單方法是在命令之前呼叫 ob_start(),然後在命令之後呼叫 ob_end_clean(),像這樣

<?php
ob_start
()
$dir = shell_exec('dir B:');
if
is_null($dir)
{
// B: 不存在
// 在這裡對 stderr 輸出執行任何您想做的操作
}
else
{
// B: 存在,$dir 保留目錄清單
// 在這裡對其執行任何您想做的操作
}
ob_end_clean(); // 清除證據 :-)
?>

如果 B: 不存在,則 $dir 將為 Null,並且輸出緩衝區將捕獲該訊息

「系統找不到指定的路徑」。

(至少在 WinXP 下是這樣)。如果 B: 存在,則 $dir 將包含目錄清單,我們可能不在乎輸出緩衝區。在任何情況下,都需要在繼續之前將其刪除。
smcbride at msn dot com
3 年前
從 PHP 7.4 開始,proc_open 可能是大多數用例的更好解決方案。它具有更好的控制和平台獨立性。如果您仍然想使用 shell_exec(),我喜歡使用一個允許更好控制的函式來包裝它。

如下所示的程式碼可以解決 apache/php 上背景處理的一些問題。它也

public function sh_exec(string $cmd, string $outputfile = "", string $pidfile = "", bool $mergestderror = true, bool $bg = false) {
$fullcmd = $cmd;
if(strlen($outputfile) > 0) $fullcmd .= " >> " . $outputfile;
if($mergestderror) $fullcmd .= " 2>&1";
if($bg) {
$fullcmd = "nohup " . $fullcmd . " &";
if(strlen($pidfile)) $fullcmd .= " echo $! > " . $pidfile;
} else {
if(strlen($pidfile) > 0) $fullcmd .= "; echo $$ > " . $pidfile;
}
shell_exec($fullcmd);
}
eric dot peyremorte at iut-valence dot fr
17 年前
我在使用重音符號和 shell_exec 時遇到問題。

例如

從 shell 執行此命令

/usr/bin/smbclient '//BREZEME/peyremor' -c 'dir' -U 'peyremor%*********' -d 0 -W 'ADMINISTRATIF' -O 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192' -b 1200 -N 2>&1

給了我這個

影片 D 0 2007年6月12日 星期二 14:41:21
桌面 DH 0 2007年6月18日 星期一 17:41:36

像這樣使用 php

shell_exec("/usr/bin/smbclient '//BREZEME/peyremor' -c 'dir' -U 'peyremor%*******' -d 0 -W 'ADMINISTRATIF' -O 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192' -b 1200 -N 2>&1")

給了我這個

影片 桌面 DH 0 2007年6月18日 星期一 17:41:36

這兩行是從重音符號所在的位置連接起來的。

我找到了解決方案:php 預設使用 LOCALE=C 來執行命令。

我只是在 shell_exec 之前添加了以下幾行,問題就解決了

$locale = 'fr_FR.UTF-8';
setlocale(LC_ALL, $locale);
putenv('LC_ALL='.$locale);

只需將其調整為您語言的地區設定。
哪個 Shell?
2 年前
有沒有任何地方提到 shell_exec() 執行的是哪個 SHELL?...在我的系統上,它執行的是 sh,而不是 bash,這導致 <() 程序替換失敗,且沒有錯誤訊息。

文件不應該提及此函數(及反引號 ``)執行的是哪個 shell 嗎?
kamermans at teratechnologies dot net
17 年前
我不確定使用此函數會得到哪個 shell,但您可以這樣找出

<?php
$cmd
= 'set';
echo
"<pre>".shell_exec($cmd)."</pre>";
?>

在我的 FreeBSD 6.1 機器上,我得到這個

USER=root
LD_LIBRARY_PATH=/usr/local/lib/apache2
HOME=/root
PS1='$ '
OPTIND=1
PS2='> '
LOGNAME=root
PPID=88057
PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin
SHELL=/bin/sh
IFS='
'

非常有趣。請注意,PATH 可能不像您需要的那麼完整。我想要通過 ImageMagik 的 "convert" 來執行 Ghostscript,最終必須在執行命令之前添加我的路徑

<?php
$cmd
= 'export PATH="/usr/local/bin/"; convert -scale 25%x25% file1.pdf[0] file2.png 2>&1';
echo
"<pre>".shell_exec($cmd)."</pre>";
?>

此外,請注意 shell_exec() 不會抓取 STDERR,因此請使用 "2>&1" 將其重新導向至 STDOUT 並捕獲它。
RoBorg
17 年前
Subversion 錯誤 "svn: Can't recode string" 可能是因為地區設定錯誤所導致。試試看
<?php
putenv
('LANG=en_US.UTF-8');
?>
(或您偏好的任何地區設定)在您呼叫 shell_exec() 之前
pedroxam at gmail dot com
5 年前
這是我的總結

function execCommand($command, $log) {

if (substr(php_uname(), 0, 7) == "Windows")
{
//windows
pclose(popen("start /B " . $command . " 1> $log 2>&1", "r"));
}
else
{
//linux
shell_exec( $command . " 1> $log 2>&1" );
}

return false;
}
joelhy
15 年前
這是一個抓取 STDERR 並丟棄 STDOUT 的簡單方法
在您的 shell 命令末尾添加 '2>&1 1> /dev/null'

例如
<?php
$output
= shell_exec('ls file_not_exist 2>&1 1> /dev/null');
?>
jack dot harris-7ot2j4ip at yopmail dot Com
10 年前
在 Windows 上,如果 shell_exec 沒有返回您預期的結果,而且電腦位於企業網路中,請將 Apache 服務(或 wampapache)設定為在您的帳戶下執行,而不是「本機系統帳戶」。您的帳戶必須具有管理員權限。

若要變更帳戶,請前往主控台服務,在 Apache 服務上按一下滑鼠右鍵,選擇內容,然後選取連線索引標籤。
codeslinger at compsalot dot com
14 年前
我花了很長時間才解決這個問題,所以我想在這裡提到它。

如果您正在使用 Eclipse,並且嘗試執行類似的操作

<?php

$out
= shell_exec("php -s $File"); //這會失敗
?>

在 Eclipse 除錯工具中執行時,它總是會失敗。這在 Linux 和 Windows 上都會發生。我最終將問題歸咎於 Eclipse 在除錯時對環境所做的變更。

解決方法是強制設定 ini。如果您不需要 ini,則 -n 就足夠了。

<?php

$out
= shell_exec("php -n -s $File"); //這會正常運作
?>

當然,如果您在除錯工具外執行它,則無需 -n 就可以正常運作。您可能想要使用除錯旗標來控制此行為。
tonysb at gmx dot net
22 年前
在 virtual() 函數不可用時(例如 Microsoft IIS),shell_exec 非常適合做為替代方案。您只需要移除標頭中傳送的內容類型字串

<?
$mstrng = shell_exec('yourcgiscript.cgi');
$mstrng = ereg_replace( "Content-type: text/html", "", $mstrng );
echo $mstrng;
?>

這對我來說可以很好地替代 SSI 或 virtual() 函數。

Anton Babadjanov
http://www.vbcn.com.ar
saivert at saivert dot com
16 年前
如何在 Windows 上取得磁碟機的磁碟區標籤

<?php

function GetVolumeLabel($drive) {
// 嘗試抓取磁碟區名稱
if (preg_match('#Volume in drive [a-zA-Z]* is (.*)\n#i', shell_exec('dir '.$drive.':'), $m)) {
$volname = ' ('.$m[1].')';
} else {
$volname = '';
}
return
$volname;
}

print
GetVolumeLabel("c");

?>

注意:此正規表示式假設使用的是英文版 Windows。請針對不同本地化的 Windows 版本進行相應修改。
匿名
19 年前
請小心您如何將權限提升到您的 php 腳本。謹慎和規劃是一個好主意。很容易打開巨大的安全漏洞。以下是我從實驗和 Unix 文件中收集的一些有用提示。

需要考慮的事項

1. 如果您在 Unix 中以 Apache 模組執行 php,那麼您執行的每個系統命令都是以使用者 apache 的身分執行。這很合理.. Unix 不允許以這種方式提升權限。如果您需要以提升的權限執行系統命令,請仔細思考問題!

2. 如果您決定以 root 身分執行 apache,那您絕對是瘋了。您還不如自己打自己臉。總是有更好的方法來做這件事。

3. 如果您決定使用 SUID,最好不要 SUID 腳本。在許多 Unix 版本上,腳本的 SUID 已停用。SUID 腳本會打開安全漏洞,因此即使這是一個選項,您也不一定總是想走這條路。撰寫一個簡單的二進位檔案,並將該二進位檔案的權限提升為 SUID。我個人認為透過 SUID 傳遞系統命令是一個很可怕的想法 - 也就是說讓 SUID 接受命令名稱做為參數。您還不如以 root 身分執行 Apache!
rustleb at hotmail dot com
18 年前
對於在您不在乎中間檔案時捕獲 stdout 和 stderr,我使用 . . . 的效果更好
<?php
function cmd_exec($cmd, &$stdout, &$stderr)
{
$outfile = tempnam(".", "cmd");
$errfile = tempnam(".", "cmd");
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("file", $outfile, "w"),
2 => array("file", $errfile, "w")
);
$proc = proc_open($cmd, $descriptorspec, $pipes);

if (!
is_resource($proc)) return 255;

fclose($pipes[0]); //Don't really want to give any input

$exit = proc_close($proc);
$stdout = file($outfile);
$stderr = file($errfile);

unlink($outfile);
unlink($errfile);
return
$exit;
}
?>

這與重新導向沒什麼太大區別,只是它會為您處理暫存檔(您可能需要將目錄從「.」變更),並且由於 proc_close 呼叫,它會自動封鎖。這模擬了 shell_exec 的行為,並讓您取得 stderr。
mcbeth at broggs dot org
21 年前
至於最後一個範例的錯誤檢查。幾個 shell 都有 && 運算子,因此您只需使用它,而不是 ; 將您的命令串在一起。如果任何程式在任何時候失敗,您將會返回,而不會執行其餘程式。
Numabyte
3 年前
一個需要考慮的重要事項是,在某些情況下,例如在 MacOS 上,當您將命令包在括號中時,使用「2>&1」從 exec() 正確輸出字串會運作良好。

例如
在 CMD 周圍加上括號
$cmd = "(mysqldump -h l ocalhost -u root -ppassword --databases login_practice_db > /Users/username/Desktop/testDB.sql) 2>&1";

$sys = shell_exec($cmd ); // 運作並產生錯誤 (localhost 的間距刻意不良)

在 CMD 周圍不加括號
$cmd = "mysqldump -h l ocalhost -u root -ppassword --databases login_practice_db > /Users/username/Desktop/testDB.sql 2>&1";

$sys = shell_exec($cmd ); // 沒有作用,並如同結果沒有錯誤般空白。空字串。

shell 命令的執行如何歸結為對字串剖析可能需要多細微,這很有趣。
wally at soggysoftware dot co dot uk
12 年前
正如其他人所指出的,如果執行的命令沒有輸出任何內容,shell_exec 和反引號運算子 (`) 都會傳回 NULL。

這可以透過執行如下任何操作來解決

shell_exec ("silentcmd && echo ' '");

如果命令成功,我們在這裡只是輸出空白字元 - 這解決了這個稍微奇怪的問題。從那裡,您可以 trim() 命令輸出等等。
jessop <AT> bigfoot <DOT> com
21 年前
對於那些嘗試在類 Unix 平台上使用 shell_exec 但似乎無法使其運作的人,請快速提醒一下。PHP 在系統上以網頁使用者身分執行(通常是 Apache 的 www),因此您需要確保網頁使用者對您嘗試在 shell_exec 命令中使用的任何檔案或目錄具有權限。否則,它看起來不會執行任何動作。
Martin Rampersad
20 年前
我有 PHP (CGI) 和 Apache。我也 shell_exec() 使用 PHP CLI 的 shell 腳本。此組合會破壞從呼叫傳回的字串值。我得到二進位垃圾。以 #!/usr/bin/bash 開頭的 shell 腳本會正確傳回其輸出。

一個解決方案是強制建立乾淨的環境。PHP CLI 不再具有 CGI 環境變數可受阻。

<?php

// 二進位垃圾。
$ExhibitA = shell_exec('/home/www/myscript');

// 完美。
$ExhibitB = shell_exec('env -i /home/www/myscript');

?>

-- start /home/www/myscript
#!/usr/local/bin/phpcli
<?php

echo("Output.\n");

?>
-- end /home/www/myscript
gabekon at gmail dot com
16 年前
回覆:kamermans 的筆記,

我在使用 shell_exec 時,PATH 變數也遇到類似的問題。即使使用硬編碼的二進位檔案完整路徑,我也會收到找不到 .so 檔案的錯誤。經過一些研究,我了解到我必須設定 LD_LIBRARY_PATH 變數

<?php

$command
= 'export LD_LIBRARY_PATH="' . $path_to_library_dir .'"; ' . $path_to_binary;
shell_exec($command);

?>

希望這能為某些人省去一些麻煩,

- G
ruan at nospam dot tillcor dot com
18 年前
當遵循 Kenneth 在本頁提及的透過 nanoweb 伺服器執行根腳本的方法時,您很可能需要能夠執行像 lynx 這樣的文字模式瀏覽器,並將 php 腳本傳遞給它(運作良好)。

在掙扎了一段時間(lynx 一直要求我下載檔案而不是執行它)之後,我意識到我還必須安裝 php-cgi,並修改 nanoweb 設定檔以使用該 php 直譯器,而不是 /usr/bin/php。(在 Debian 上,這是 CLI 版本)。

在 Ubuntu 6.06 上

apt-get install php5-cgi

在編輯 /etc/nanoweb/nanoweb.conf 並快速重新啟動 Web 伺服器後,lynx 和 links 將會正確執行您的 PHP 腳本。

希望這對其他人有幫助:)

Ruan Fourie
phillipberry at NOSPAM dot blisswebhosting dot com
19 年前
我發現了一些奇怪的事情。

如果您執行 exec,然後直接執行 shell_exec,shell_exec 將根本不會執行,並且會傳回 NULL。

為了讓它運作,我在 exec 之後加入了 sleep(5),現在 shell_exec 運作正常。
smithnigelw at gmail dot com
6 年前
在 Windows 上使用 PHP 時,如果您收到「警告:shell_exec() [function.shell-exec]:無法執行」錯誤,則您需要檢查檔案「C:\WINDOWS\system32\cmd.exe」的權限。您需要對此檔案具有讀取/執行權限。我建議使用 sysinternals Process Monitor 「procmon.exe」來確認嘗試執行「cmd.exe」的使用者。篩選「處理程序名稱」為「php-cgi.exe」且「路徑」以「cmd.exe」結尾。查看存取遭拒錯誤的工作的事件屬性,它會顯示「模擬」使用者名稱。這通常是「Internet Guest Account」,通常是「NT AUTHORITY\IUSR」。
Paul Cook
18 年前
Nathan De Hert 在下方提到的技術相當不安全 - 您絕不應該將密碼留在 apache 使用者可讀取的檔案中。

如果您需要在 *nix 系統上使用此類功能,請查看 /etc/sudo 檔案(使用命令「visudo」編輯)。NOPASSWD 標籤允許其他使用者以根身分執行指定的命令,而無需指定密碼。這需要一些額外的設定,但更安全。
rigsbr at yahoo dot com dot br
18 年前
如果您需要執行未經授權的命令,且無法透過 ssh 執行或安裝任何擴充功能,則在 Apache 1.3.x 和 PHP 4 中有一種方法。
在 cgi-bin 目錄中建立檔案,如下所示

#!/usr/bin/php
<?
echo shell_exec('whoami');
?>

別忘了將您建立的檔案設定為執行權限。因此,從瀏覽器呼叫它,您將會看到此腳本將由 shell 使用者執行,而不是使用者 nobody(如果執行 PHP 腳本,則為 apache 預設使用者)。
James McCormack
20 年前
我有一個 Perl 程式,從命令列執行時沒問題,但使用 shell_exec()、exec() 或 system() 時卻不行,沒有任何回傳值。我使用的是完整路徑,而且權限也設定正確。

後來發現是 Perl 程式使用的記憶體超出我的 PHP.INI 檔案設定的允許值。增加 "memory_limit" 就解決了問題。
dburles@NOSPAMgmailDOTcom
15 年前
在 Windows 中擷取錯誤輸出的簡單方法

// 我們將以執行一個 php 腳本為例
$out = shell_exec("php test.php 2> output");
print $out ? $out : join("", file("output"));

在這個例子中,我們假設如果腳本產生輸出,則表示它已正常執行,$out 變數將包含輸出。如果 $out 為空,那麼我們將從名為 'output' 的檔案讀取擷取的錯誤輸出。

希望這對某些人有幫助
Ashraf Kaabi
18 年前
我寫了一個完整的類別,用於在背景執行、終止 PID、檢查是否正在執行

<?php
/**
* @author Ashraf M Kaabi
* @name Advance Linux Exec
*/
class exec {
/**
* 在背景執行應用程式
*
* @param unknown_type $Command
* @param unknown_type $Priority
* @return PID
*/
function background($Command, $Priority = 0){
if(
$Priority)
$PID = shell_exec("nohup nice -n $Priority $Command > /dev/null & echo $!");
else
$PID = shell_exec("nohup $Command > /dev/null & echo $!");
return(
$PID);
}
/**
* 檢查應用程式是否正在執行!
*
* @param unknown_type $PID
* @return boolen
*/
function is_running($PID){
exec("ps $PID", $ProcessState);
return(
count($ProcessState) >= 2);
}
/**
* 終止應用程式的 PID
*
* @param unknown_type $PID
* @return boolen
*/
function kill($PID){
if(
exec::is_running($PID)){
exec("kill -KILL $PID");
return
true;
}else return
false;
}
};
?>
concept at conceptonline dot hu
19 年前
有趣的是,如果你執行一個不在你的路徑中的腳本(或者你輸入錯誤,或者腳本根本不存在),你將不會得到任何回傳值。錯誤將被記錄到你的網頁伺服器的 error_log 中。

有人可以添加一個註解,說明如何(如果可以的話)覆寫此行為,因為標準行為並非真正萬無一失。
php [AT] jsomers [DOT] be
19 年前
<?php

/**
* PHP 終止進程
*
* 有時,當腳本不應該繼續執行時,它可能會持續執行,而且在我們關閉瀏覽器或關閉電腦後也不會停止。因為使用 SSH 並非總是容易的,所以這是一個替代方案。
*
* @author Jensen Somers <php@jsomers.be>
* @version 1.0
*/

class KillAllProcesses {
/**
* 建構類別
*/
function killallprocesses() {
$this->listItems();
}

/**
* 列出所有項目
*/
function listItems() {
/*
* PS Unix 命令用於報告進程狀態
* -x 選擇沒有控制 ttys 的進程
*
* 輸出將如下所示:
* 16479 pts/13 S 0:00 -bash
* 21944 pts/13 R 0:00 ps -x
*
*/
$output = shell_exec('ps -x');

$this->output($output);

// 將每一行放入陣列
$array = explode("\n", $output);

$this->doKill($array);
}

/**
* 列印進程清單
* @param string $output
*/
function output($output) {
print
"<pre>".$output."</pre>";
}

/**
* 終止所有進程
* 應該可以在此進行過濾,但我現在不會這樣做。
* @param array $array
*/
function doKill($array) {
/*
* 因為我們的 $output 的第一行會如下所示
* PID TTY STAT TIME COMMAND
* 我們將跳過這一行。
*/
for ($i = 1; $i < count($array); $i++) {
$id = substr($array[$i], 0, strpos($array[$i], ' ?'));
shell_exec('kill '.$id);
}
}
}

new
KillAllProcesses();

?>

這不是最好的解決方案,但我曾經在需要快速解決問題時使用過幾次。
請注意,我終止了所有進程,在我的伺服器上,px -x 只會回傳大約 4 次 /sbin/apache,而且終止它們是相當安全的,不會有任何問題。
martin at intelli-gens dot com
14 年前
在耗費大量時間試圖找出 shell_exec 無法正常運作的原因後,我發現,在我的情況下,需要設定一些東西(這些東西通常是預設設定好的)。

選項 -jo
-j 表示:不要重建在封存檔中找到的路徑
-o 表示:始終覆寫檔案

我需要指定目的地路徑(即使未指定時應該在相同目錄中解壓縮),這可以使用 -d [路徑] 完成

奇怪的是,當我在命令列中給出命令時,我不需要放置這些選項,只有當我使用 shell_exec 呼叫它時才需要。

所以完整的 php 命令對我來說會是

shell_exec('unzip -jo /path_to_archive/archive.zip -d /destination_path')

在前面加上 echo 會對你很有幫助。
它應該會告訴你類似以下內容


Archive: /path_to_zip/archive.zip
inflating: /destination_path/file1.jpeg
inflating: /destination_path/file2.jpeg
inflating: /destination_path/file3.jpeg
To Top