背景
在go中为多台设备配置时可以定义一个数组解决,如果设备到几十或者上百台时我们改怎么办了?我们可以将设备信息和命令配置在文本中,下面跟笔者一起看一下 go 是如何通过文本中的信息批量配置设备的吧。
系统环境
- win 11
- go 1.18.1
配置脚本
创建go文件
笔者创建了一个名为 login_sw 的文件夹,在文件下创建一个名为 login_sw.go 的文件,将下列代码复制到文件中并保存,效果如图所示:
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"time"
"golang.org/x/crypto/ssh"
)
func loginHosts(host_file string) ([]string, error) {
var hostList []string
// 获取当前路径
path_file, _ := os.Getwd()
// Go读取文件时需要使用绝对路径
hf, err := os.Open(path_file + host_file)
// 判断结果是否唯空
if err != nil {
return nil, err
}
// 读取文本中的信息
scanner := bufio.NewScanner(hf)
// 将文本中的内容切片,每行切成一个
scanner.Split(bufio.ScanLines)
// 遍历切片内容,将切片后的信息写入 hostList 数组中
for scanner.Scan() {
hostList = append(hostList, scanner.Text())
}
// 打印获取到的IP地址
hf.Close()
fmt.Printf("获取的设备ip是: %v \n\n", hostList)
return hostList, nil
}
func readCommand(cmd_file string) ([]string, error) {
var cmdList []string
// 获取当前路径
path_file, _ := os.Getwd()
// Go读取文件时需要使用绝对路径
hf, err := os.Open(path_file + cmd_file)
// 判断结果是否唯空
if err != nil {
return nil, err
}
// 读取文本中的信息
scanner := bufio.NewScanner(hf)
// 将文本中的内容切片,每行切成一个
scanner.Split(bufio.ScanLines)
// 遍历切片内容,将切片后的信息写入 cmdList 数组中
for scanner.Scan() {
cmdList = append(cmdList, scanner.Text())
}
// 打印获取到的命令
hf.Close()
fmt.Printf("获取的命令是: %v \n\n", cmdList)
return cmdList, nil
}
func connect_device(device_ip string, command []string) (string, error) {
var b bytes.Buffer
user := "admin"
pass := "qazxsw@123"
targethost := device_ip + ":22"
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.Password(pass),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
fmt.Printf("开始登录设备,设备IP是: %s \n", device_ip)
conn, err := ssh.Dial("tcp", targethost, config)
//用于故障排查
// time.Sleep(1 * time.Second)
//fmt.Println(conn)
if err != nil {
return (device_ip + " Failed to dial"), err
}
sess, err := conn.NewSession()
// 延迟关闭连接,函数运行完成后再关闭
defer conn.Close()
if err != nil {
return (device_ip + " Failed to create session"), err
}
stdin, err := sess.StdinPipe()
if err != nil {
return (device_ip + " Failed to create standard input"), err
}
// 用于存储设备回显信息
sess.Stdout = &b
// 异常信息直接打印出来
sess.Stderr = os.Stderr
// 向设备推送命令
sess.Shell()
for _, line := range command {
fmt.Fprintf(stdin, "%s\n", line)
}
fmt.Fprintf(stdin, "quit\n")
// 等待会话完成
sess.Wait()
// 关闭会话
sess.Close()
// 返回显信息
return b.String(), nil
}
func main() {
start_t := time.Now()
// 获取文本中的命令信息
commandList, err := readCommand("/cmd.cfg")
if err != nil {
// log.Fatal 遇到异常后直接退出脚本并打印错误信息
log.Fatal("read command file err info is : ", err)
}
// 获取文本中的设备地址
hostList, err := loginHosts("/host_file001.txt")
if err != nil {
// log.Fatal 遇到异常后直接退出脚本并打印错误信息
log.Fatal("read host file err info is : ", err)
}
for _, host := range hostList {
// 连接设备发送命令
result, err := connect_device(host, commandList)
if err != nil {
fmt.Printf("错误信息是:%s %s \n\n", result, err)
}
}
fmt.Printf("\n脚本运行时长是:%s", time.Since(start_t))
// fmt.Println(time.Now().Sub(start_t)) 打印时长的第二种方法
}
编译环境
- 通过终端将目录切换至脚本文件下
- 执行下列代码
go mod init login_sw // 使用`go mod init hello`初始化生成 go.mod 文件
go mod tidy // 添加需要用到但go.mod中查不到的模块
go mod vendor // 将新增的依赖包自动写入当前项目的 vendor 目录,下载依赖
创建设备IP文本
笔者使用文件名字为 host_file001.txt,文本中新增了两台设备。
注意:改变了文件名请一并更改脚本中的文件名
创建设备配置文本
笔者使用文件名字为 cmd.cfg,文本中新增两条查询命令。
注意:改变了文件名请一并更改脚本中的文件名
关于打印模块的小细节
Println 使用默认格式说明符格式化字符串,在字符串后添加新行
Print 使用默认格式说明符打印字符串格式,但不在字符串之后添加新行
Printf 使用自定义说明符格式化字符串。它也不会添加新行
有兴趣的可以前往官问查阅更多细节 fmt package - fmt - Go Packages
运行脚本
go run .\login_sw.go
结果如下图所示:
异常处理
如果出现找不到目录错误信息,由于 os.Getwd()
方法掉用的是运行 go run
命令的路径。
设备连接失败信息
基础入门
一些开源的中文书籍 Books · golang/go Wiki · GitHub
官方文档 Documentation - The Go Programming Language