Golang实现一个批量自动化执行树莓派指令的软件(2)指令

embedded/2024/11/14 21:02:22/

简介

基于上篇 Golang实现一个批量自动化执行树莓派指令的软件(1)文本加密&配置&命令行交互实现, 这篇实现的是指令, 即通过ssh执行linux指令的实现。

环境描述

运行环境: Windows, 基于Golang, 暂时没有使用什么不可跨平台接口, 理论上支持Linux/MacOS
目标终端树莓派DebianOS(主要做用它测试)

实现

接口定义

type ICommander interface {/*Command: 堵塞直到执行完毕*/Command(cmdStr string) (output string, err error)/*CommandWithCallback:background 为 true 时 异步函数, 执行完毕之后自动调用回调background 为 false时 堵塞直到执行完毕之后调用回调*/CommandWithCallback(cmdStr string, callback func(output string, err error), background bool) error/*Cancel : 任务取消, 执行到一半的任务断开场景*/Cancel() error/*Desroy : 无论如何直接关闭客户端*/Destroy() error
}

接口实现

package sshutilimport ("fmt""golang.org/x/crypto/ssh""io""time"
)type Commander struct {sshClient *ssh.Clientstarted  boolcanceled chan struct{}finished chan struct{}
}func NewCommander(cfg SSHConfig) (*Commander, error) {sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", cfg.IP, cfg.Port), cfg.sshClientConfig)if err != nil {return nil, err}return &Commander{sshClient: sshClient,canceled:  make(chan struct{}),finished:  make(chan struct{}),}, nil
}func (c *Commander) Command(cmd string) (output string, err error) {c.doCommand(cmd, func(recvStr string, e error) {output = recvStrerr = e})return
}func (c *Commander) CommandWithCallback(cmd string, callback func(output string, e error), background bool) (err error) {if background {go c.doCommand(cmd, callback)} else {c.doCommand(cmd, callback)}return
}func (c *Commander) doCommand(cmd string, callback func(output string, e error)) {var (session   *ssh.SessionrecvBytes []byteerr       errorcanceled  bool)if session, err = c.sshClient.NewSession(); err != nil {callback("", err)return}c.started = truego func(session *ssh.Session) {select {case <-c.finished:case <-c.canceled:canceled = trueif e := session.Signal(ssh.SIGINT); nil != err {fmt.Println("emit abort fail: ", e.Error())}}}(session)defer func() {c.started = falseif !canceled {c.finished <- struct{}{}}if e := session.Close(); nil != e && e != io.EOF {fmt.Println("session close fail: ", e.Error())}}()// err = session.Start(command) // ssh库的异步方式if recvBytes, err = session.CombinedOutput(cmd); err != nil {if canceled {err = fmt.Errorf("user canceled")}callback(string(recvBytes), err)return}callback(string(recvBytes), err)
}func (c *Commander) Cancel() error {if c.started {select {case c.canceled <- struct{}{}:case <-time.After(time.Second): // 取消时间过长,取消失败return fmt.Errorf("time out waiting for cancel")}}return nil
}func (c *Commander) Destroy() error {var err = c.Cancel()close(c.finished)close(c.canceled)err = c.sshClient.Close()return err
}

测试用例

package sshutilimport ("fmt""golang.org/x/crypto/ssh""sync""testing""time"
)func newCommander() (*Commander, error) {config := &ssh.ClientConfig{User: "pi",Auth: []ssh.AuthMethod{ssh.Password("a123456"),},HostKeyCallback: ssh.InsecureIgnoreHostKey(),}sshCfg := SSHConfig{IP:              "192.168.3.2",Port:            22,User:            "pi",Password:        "a123456",sshClientConfig: config,}commander, err := NewCommander(sshCfg)return commander, err
}func TestEasyCommandAndDestroy(t *testing.T) {commander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()var output stringoutput, err = commander.Command("ls /")fmt.Println("command result: ", output)
}func TestCommandWithCallbackWithoutAsync(t *testing.T) {commander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}}, false)if nil != err {fmt.Println("do command fail: ", err.Error())return}
}func TestCommandWithCallbackAsync(t *testing.T) {var waiter sync.WaitGroupcommander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()waiter.Add(1)err = commander.CommandWithCallback("sleep 4; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}waiter.Done()}, true)if nil != err {fmt.Println("do command fail: ", err.Error())return}fmt.Println("waiting finished<--------")waiter.Wait()fmt.Println("waiting finished------>")
}func TestCommandWithCallbackAndCancel(t *testing.T) {var waiter sync.WaitGroupcommander, err := newCommander()if nil != err {fmt.Println("new command fail: ", err.Error())return}defer func() {e := commander.Destroy()if nil != err {fmt.Println("fail to destroy, ", e.Error())}}()waiter.Add(1)err = commander.CommandWithCallback("sleep 10; ls /", func(output string, e error) {if nil != e {fmt.Println("fail, ", e.Error())} else {fmt.Println("result: ", output)}waiter.Done()}, true)if nil != err {fmt.Println("do command fail: ", err.Error())return}fmt.Println("waiting finished<--------")time.Sleep(time.Second * 2)fmt.Println("canceling...")fmt.Println("canceled, ", commander.Cancel())waiter.Wait()fmt.Println("waiting finished------>")
}

代码源

https://gitee.com/grayhsu/ssh_remote_access

其他

参考


http://www.ppmy.cn/embedded/17685.html

相关文章

Redis 如何实现分布式锁

课程地址 单机 Redis naive 版 加锁&#xff1a; SETNX ${lockName} ${value} # set if not exist如果不存在则插入成功&#xff0c;返回 1&#xff0c;加锁成功&#xff1b;否则返回 0&#xff0c;加锁失败 解锁&#xff1a; DEL ${lockName}问题1 2 个线程 A、B&#…

Kotlin语法入门-访问和属性修饰符(5)

Kotlin语法入门-访问和属性修饰符(5) 文章目录 Kotlin语法入门-访问和属性修饰符(5)五、访问和属性修饰符1、kotlin修饰符2、internal3、默认修饰符4、open关键字开启继承并实现 五、访问和属性修饰符 1、kotlin修饰符 kotlin在常见的访问修饰符private&#xff0c;protected…

Java -- (part13)

一.异常 1.概述 代码出现了不正常的现象 2.分类 Throwable Error -- 错误 Exception -- 异常 a.编译时期异常:语法没有错误,调用某个方法,直接爆红(因为被调用的方法底层跑了一个编译时期异常) b.运行时期异常:语法没有错误,但是一运行就报错,RuntimeException以及…

CSS 命名规范 - BEM

CSS 命名规范 - BEM 规范化命名 CSS 的选择器按照规范命名的优点&#xff1a; 提高代码的 可读性 和 可维护性提高 可重用性可以有效地避免组件或模块间样式的相互污染&#xff0c;减少嵌套层级 BEM 格式 [prefix]-[block]__[element]--[modifier]Prefix。全局前缀&#x…

C语言例题(递归、二分查找、冒泡排序)

一、递归案例 有5个人坐在在一起&#xff0c;问第5个人多少岁&#xff1f;他说比第4个人大两岁。问第4个人岁数&#xff0c;他说比第3个人大两岁。问第3个人&#xff0c;又说比第2个人大两岁。问第2个人&#xff0c;说比第1个人大2岁。最后问第1个人&#xff0c;他说是10岁。请…

顺序表 (C语言版)

顺序存储&#xff1a; 把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中&#xff0c;元素之间的关系由存储单元的邻接关系来体现。 顺序表的特点&#xff1a; 能在O(1)的时间内找到第i个元素存储密度高拓展容量不方便插入&#xff0c;删除操作不方便 C语言中可使用&am…

树倒着打印输出

思路 先向右遍历&#xff0c;同时空格也要变多&#xff0c;那么就先prt(root->right,spacecnt) 其中space是离最左边多远,cnt是每次叠加的有多远 输出最右边端点 和 空行 再向左遍历 同样prt(root->left,spacecnt) 代码 #include <iostream> #include <st…

windows ubuntu:sed,awk,grep篇:4.执行 sed

目录 23.单行内执行多个 sed 命令 24.sed 脚本文件 25.sed 注释 26.把 sed 当做命令解释器使用 27.直接修改输入文件 23.单行内执行多个 sed 命令 第一章内已经讲过&#xff0c;单行内执行多个 sed 命令有多种方法。 1. 使用多命令选项 –e 多命令选项-e 使用方法如…