一般命令
创新互联专注于企业营销型网站建设、网站重做改版、坡头网站定制设计、自适应品牌网站建设、H5网站设计、商城网站建设、集团公司官网建设、外贸网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为坡头等各大城市提供网站开发制作服务。
所谓一般命令,就是在一定时间内会执行完的命令。比如 grep, cat 等等。 执行命令的步骤是:连接,执行,获取结果
连接
连接包含了认证,可以使用 password 或者 sshkey 2种方式来认证。下面的示例为了简单,使用了密码认证的方式来完成连接。
import (
"fmt"
"time"
"golang.org/x/crypto/ssh"
)
func connect(user, password, host string, port int) (*ssh.Session, error) {
var (
auth []ssh.AuthMethod
addr string
clientConfig *ssh.ClientConfig
client *ssh.Client
session *ssh.Session
err error
)
// get auth method
auth = make([]ssh.AuthMethod, 0)
auth = append(auth, ssh.Password(password))
clientConfig = ssh.ClientConfig{
User: user,
Auth: auth,
Timeout: 30 * time.Second,
}
// connet to ssh
addr = fmt.Sprintf("%s:%d", host, port)
if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
return nil, err
}
// create session
if session, err = client.NewSession(); err != nil {
return nil, err
}
return session, nil
}
连接的方法很简单,只要提供登录主机的 用户*, *密码*, *主机名或者IP*, *SSH端口
执行,命令获取结果
连接成功后,执行命令很简单
import (
"fmt"
"log"
"os"
"time"
"golang.org/x/crypto/ssh"
)
func main() {
session, err := connect("root", "xxxxx", "127.0.0.1", 22)
if err != nil {
log.Fatal(err)
}
defer session.Close()
session.Run("ls /; ls /abc")
}
上面代码运行之后,虽然命令正常执行了,但是没有正常输出的结果,也没有异常输出的结果。 要想显示结果,需要将 session 的 Stdout 和 Stderr 重定向 修改 func main 为如下:
func main() {
session, err := connect("root", "xxxxx", "127.0.0.1", 22)
if err != nil {
log.Fatal(err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Run("ls /; ls /abc")
}
这样就能在屏幕上显示正常,异常的信息了。
交互式命令
上面的方式无法远程执行交互式命令,比如 top , 远程编辑一个文件,比如 vi /etc/nginx/nginx.conf 如果要支持交互式的命令,需要当前的terminal来接管远程的 PTY。
func main() {
session, err := connect("root", "olordjesus", "dockers.iotalabs.io", 2210)
if err != nil {
log.Fatal(err)
}
defer session.Close()
fd := int(os.Stdin.Fd())
oldState, err := terminal.MakeRaw(fd)
if err != nil {
panic(err)
}
defer terminal.Restore(fd, oldState)
// excute command
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
panic(err)
}
// Set up terminal modes
modes := ssh.TerminalModes{
ssh.ECHO: 1, // enable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
log.Fatal(err)
}
session.Run("top")
}
在Client的字段中,有一个 CheckRedirect ,此字段就是用来控制重定向的函数,如果没有定义此字段的话,将会使用默认的 defaultCheckRedirect 方法。
默认的转发策略是最多转发10次。
在转发的过程中,某一些包含安全信息的Header,比如 Authorization 、 WWW-Authenticate 、 Cookie 等,如果转发是跨域的,那么这些Header不会复制到新的请求中。
http的重定向判断会默认处理以下状态码的请求:
301 (Moved Permanently)
302 (Found)
303 (See Other)
307 (Temporary Redirect)
308 (Permanent Redirect)
301、302和303请求将会改用Get访问新的请求,而307和308会使用原有的请求方式。
那么,我们如何去控制重定向的次数,甚至是禁止重定向呢?这里其实就需要我们自己去实现一个CheckRedirect函数了,首先我们来看看默认的defaultCheckRedirect方法:
第一个参数req是即将转发的request,第二个参数 via是已经请求过的requests。可以看到其中的逻辑是判断请求过的request数量,大于等于10的时候返回一个error,这也说明默认的最大重定向次数为10次,当此函数返回error时,即是重定向结束的时候。
所以如果需要设置重定向次数,那么复制一份这个函数,修改函数名字和其中if判断的数字,然后在生成Client时设定到Client即可:
或者:
禁止重定向则可以把判断数字修改为0。最好相应地修改errors中提示的信息。
管道和重定向输入的数据都是通过标准输入传入程序的, os.Stdin 即为标准输入。
可以通过 golang.org/x/crypto/ssh/terminal 的 terminal.IsTerminal(0) 判断是否是管道和重定向输入,为什么是 0 :因为标准输入的文件描述符是 0
为 true 时表示是交互式环境
为 false 时是我们要的场景
首先需要安装 golang.org/x/crypto/ssh/terminal 这个包(安装时需要 VPN 的辅助):
go get golang.org/x/crypto/ssh/terminal
也可以使用 github.com/mattn/go-isatty 这个包:
!isatty.IsTerminal(os.Stdin.Fd())
下面是示例代码:
package mainimport (
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
"golang.org/x/crypto/ssh/terminal")func main() {
flag.Parse()
data := flag.Args()
if !terminal.IsTerminal(0) {
b, err := ioutil.ReadAll(os.Stdin)
if err == nil {
data = append(data, string(b))
}
}
fmt.Println(strings.Join(data, " "))}
测试效果:
$ echo "hello" hello.txt
$ go run main.go hello world # 参数输入hello world
$ cat hello.txt | go run main.go # 管道输入hello
$ go run main.go hello.txt # 重定向输入hello
$