go 交互执行shell命令

yuyu888 于 2021-01-04 发布

背景

当我执行一些shell命令时, 经常会要求我们输入yes 或者 用户名/密码;如果我们想要编写一个自动化运行的shell脚本,希望程序自动帮助我们填写,原生的shell是不支持的, 需要单独安装expect; 可以参考:expect自动输入密码、命令

由于目前多用go语言开发,而且go也很容易打包成可执行文件,操作起来非常方便,并且可以结合shell和本身的语法完成更加复杂的业务, 所以本文主要介绍通过go来实现交互执行shell命令;

gexpect

以ssh 为例, 主要是通过 github.com/ThomasRooney/gexpect 来实现

gossh.go

package main

import (
	"log"
	"time"

	"github.com/ThomasRooney/gexpect"
)

func main() {
	// cmd := "sh ./test.sh"
    cmd := `ssh username@server_ip`
    // 	cmd := `ssh username@server_ip "ls"` // 登录后执行ls 命令
	pwd := "yourpassword"

	child, err := gexpect.Spawn(cmd)
	if err != nil {
		log.Fatal("Spawn cmd error ", err)
	}

	if err := child.ExpectTimeout("password:", 10*time.Second); err != nil {
		log.Fatal("Expect timieout error ", err)
	}

	if err := child.SendLine(pwd); err != nil {
		log.Fatal("SendLine password error ", err)
	}

    // child.Expect("password:")
	// child.SendLine(pwd)
	child.Interact()
	child.Close()
}

os/exec

    type Cmd struct {
	Path         string   //运行命令的路径,绝对路径或者相对路径
	Args         []string   // 命令参数
	Env          []string         //进程环境,如果环境为空,则使用当前进程的环境
	Dir          string   //指定command的工作目录,如果dir为空,则comman在调用进程所在当前目录中运行
	Stdin        io.Reader  //标准输入,如果stdin是nil的话,进程从null device中读取(os.DevNull),stdin也可以时一个文件,否则的话则在运行过程中再开一个goroutine去
             //读取标准输入
	Stdout       io.Writer       //标准输出
	Stderr       io.Writer  //错误输出,如果这两个(Stdout和Stderr)为空的话,则command运行时将响应的文件描述符连接到os.DevNull
	ExtraFiles   []*os.File   
	SysProcAttr  *syscall.SysProcAttr
	Process      *os.Process    //Process是底层进程,只启动一次
	ProcessState *os.ProcessState  //ProcessState包含一个退出进程的信息,当进程调用Wait或者Run时便会产生该信息.
}

func ExecTest

func ExecTest() {
	cmd := exec.Command("sh", "./test.sh")
	cmd.Stdin = strings.NewReader("127.0.0.1\n 3306\n") //多次输入
	var out bytes.Buffer
	cmd.Stdout = &out //输出

	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf(out.String())
}

test.sh

#!/bin/bash
read -p "ip:" IP 
echo "ip:"$IP
read -p "port:" PORT
echo "====="
echo $IP":"$PORT
echo "====="

官方ssh工具

golang.org/x/crypto/ssh

【example】

package main

import (
	"bytes"
	"fmt"
	"golang.org/x/crypto/ssh"
	"log"
)

func main() {
	var hostKey ssh.PublicKey
	// An SSH client is represented with a ClientConn.
	//
	// To authenticate with the remote server you must pass at least one
	// implementation of AuthMethod via the Auth field in ClientConfig,
	// and provide a HostKeyCallback.
	config := &ssh.ClientConfig{
		User: "username",
		Auth: []ssh.AuthMethod{
			ssh.Password("yourpassword"),
		},
		HostKeyCallback: ssh.FixedHostKey(hostKey),
	}
	client, err := ssh.Dial("tcp", "yourserver.com:22", config)
	if err != nil {
		log.Fatal("Failed to dial: ", err)
	}
	defer client.Close()
    // Each ClientConn can support multiple interactive sessions,
	// represented by a Session.
	session, err := client.NewSession()
	if err != nil {
		log.Fatal("Failed to create session: ", err)
	}
	defer session.Close()

	// Once a Session is created, you can execute a single command on
	// the remote side using the Run method.
	var b bytes.Buffer
	session.Stdout = &b
	if err := session.Run("/usr/bin/whoami"); err != nil {
		log.Fatal("Failed to run: " + err.Error())
	}
	fmt.Println(b.String())
}

推荐阅读:

https://www.cnblogs.com/chenqionghe/p/8267326.html

另一个封装:

https://github.com/scottkiss/gosshtool

http://www.cocosk.com/articles/2016/6/18/go-ssh-client-1.html