有一个特殊需求,需要远程在管理多台主机,执行一些命令操作,又不想在远程机器上多装程序或者做接口。考虑了几个方案,nginx+lua,nginx+python,都比较麻烦。想来想去,干脆直接php执行shell好了。查了一下,有libssh2+ssh2的方案,了解之后觉得还不错,只需要在主控一端安装,并且是熟悉的PHP。于是装了个试试,封装了一下常用的几个ssh操作。

在Mac OS X上,通过brew安装libssh2brew install libssh2 --build-from-source,安装好之后使用pecl安装ssh2扩展,pecl install ssh2 channel://pecl.php.net/ssh2-0.12,没有开启pecl的后面看通过源码安装,安装成功之后到php.ini当中添加extension,然后通过php -i | grep ssh看一下是不是有ssh了。

在Linux上,比如CentOS,可以通过yum install libssh2 libssh2-devel来安装。php的ssh扩展除了通过上方pecl方式安装之外,可以通过源码安装。从http://pecl.php.net/package/ssh2下载最新的源码包后参考下面的过程安装

tar vxzf ssh2-version.tgz
cd ssh2-version
phpize
./configure --with-ssh2
make
make install

接下来到您php安装的扩展目录当中去找刚编译出来的so文件,比如我的在/usr/local/php/lib/php/extensions/no-debug-zts-20121212/目录下ssh2.so。到php.ini中添加一行extention=/path/to/ssh2.so。这样安装之后同样可以通过php -i | grep ssh查看是否成功安装模块。

下面的内容是我封装的常用的ssh操作,包括执行命令,sftp的get和put,文件操作,包括mkdir,rmdir,rename,rm,file。具体看代码吧。

<?php
/**
 * User: chenzhidong
 * Date: 14-8-20
 * Time: 21:25
 */ 
class SSH {
    private $connection;
    
    private $host;
    private $port;
    private $username;
    private $password;
    
    function __construct($host,$port,$username,$password){
        $this->host=$host;
        $this->port=$port;
        $this->username=$username;
        $this->password=$password;
    }
    
    private function connect(){
        if(!$this->connection)
        {
            $this->connection=ssh2_connect($this->host,$this->port);
            if($this->connection)
            {
                if(!ssh2_auth_password($this->connection,$this->username,$this->password))
                    throw new Exception('Fail to login with {$this->username}:PASSWORD[YES]');
            }
            else
                throw new Exception("Fail connect server {$this->host}:{$this->port}");
        }
    }
    
    public function execute($command){
        if(!$this->connection)
            $this->connect();
        $stream=ssh2_exec($this->connection,$command);
        stream_set_blocking($stream,true);
        return trim(stream_get_contents($stream));
    }
    
    public function shell($type='xterm',$env=array(),$width=80,$height=25,$width_height_type=SSH2_TERM_UNIT_CHARS){
        if (!$this->connection)
            $this->connect();
        return ssh2_shell($this->connection,$type,$env,$width,$height,$width_height_type);
    }
    
    public function put($remote_path,$local_path,$mode=0644){
        if (!$this->connection)
            $this->connect();
        return ssh2_scp_send($this->connection,$local_path,$remote_path,$mode);
    }
    
    public function get($remote_path,$local_path){
        if (!$this->connection)
            $this->connect();
        return ssh2_scp_recv($this->connection,$remote_path,$local_path);
    }
    
    public function file($file){
        if (!$this->connection)
            $this->connect();
        $sftp = ssh2_sftp($this->connection);
        if ($sftp)
            return ssh2_sftp_stat($sftp,$file);
        else
            return false;
    }
    
    public function mkdir($path,$mode=0777){
        if(!$this->connection)
            $this->connect();
        $sftp = ssh2_sftp($this->connection);
        if ($sftp)
            return ssh2_sftp_mkdir($sftp,$path,$mode,true);
        else
            return false;
    }
    
    public function rmdir($path){
        if (!$this->connection)
            $this->connect();
        $sftp=ssh2_sftp($this->connection);
        if($sftp)
            return ssh2_sftp_rmdir($sftp,$path);
        else
            return false;
    }
    
    public function mv($old,$new){
        if (!$this->connection)
            $this->connect();
        $sftp = ssh2_sftp($this->connection);
        if ($sftp)
            return ssh2_sftp_rename($sftp,$old,$new);
        else
            return false;
    }
    
    public function rm($file){
        if (!$this->connection)
            $this->connect();
        $sftp = ssh2_sftp($this->connection);
        if ($sftp)
            return ssh2_sftp_unlink($sftp,$file);
        else
            return false;
    }
}

虽说通过这个ssh2执行命令是没有问题,但是对于返回内容的处理还是比较麻烦的,实际返回内容是命令执行后显示的字符串,通过函数能够得到的也是完整的内容字符串,因此上面的封装当中也是直接将字符串内容返回,至于这当中的内容要怎么处理只能在后面的操作当中另行处理了,也建议在执行一些命令时不妨通过管道将内容通过grep或者awk或者sed处理一下,这样在判断结果时候可能会略方便一些。