使用pty和io的SSH脚本复制到带有菜单的流输出

如何解决使用pty和io的SSH脚本复制到带有菜单的流输出

我有两个需要帮助的问题。我想我已经将其限制在goroutine中使用io.Copy了。行为就像需要使用Enter键“重新激活” stdin。

  1. 在循环的第一次迭代中,所有操作均按预期进行。它登录到localhost,运行命令,goroutine打印stdout和stderr。但是在随后的迭代中,我必须首先按Enter键以“退出goroutine”,然后在提示符下输入响应。我怀疑这与os.Stdin有关。有人可以提供一些建议吗?

  2. 是否可以在不中断脚本的情况下向ssh命令发送sigterm?例如,我可以中断ping输出,然后将其放回菜单提示符吗?

感谢您可以提供的任何帮助。这是我在这里的第一篇文章。

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "os"
    "time"

    "golang.org/x/crypto/ssh"
)

var (
    stdout,stderr io.Reader
    stdin          io.WriteCloser
    timeout        = 30 * time.Second
)

func sshConfig() *ssh.ClientConfig {
    config := &ssh.ClientConfig{
        User: MYUSER,Auth: []ssh.AuthMethod{
            ssh.Password(MYPASS),},HostKeyCallback: ssh.HostKeyCallback(func(hostname string,remote net.Addr,key ssh.PublicKey) error { return nil }),Timeout:         timeout,}
    return config
}

func options() {
  menuString := `
  Enter 1 for "uptime"
  Enter 2 for "ping -c 20 www.google.com"
  Type exit to exit
  `
    for i := 0; i <= 5; i++ {
        var num,cmd string
        fmt.Println(menuString)
        if i == 0 {
            fmt.Print("Select an option from above: ")
        } else {
            fmt.Println("You'll have to press enter before entering your option. Why?")
            fmt.Print("Select an option from above: ")
        }

        fmt.Scanln(&num)
        switch num {
        case "1":
            cmd = "uptime"
            connect(cmd)
        case "2":
            cmd = "ping -c 20 www.google.com"
            connect(cmd)
        case "exit":
            fmt.Println("exiting")
            os.Exit(0)
        default:
            fmt.Println("Invalid response")
            fmt.Println("Select an option from above: ")
        }
    }
}

func connect(c string) {
    config := sshConfig()
    conn,err := ssh.Dial("tcp","127.0.0.1:22",config)
    defer conn.Close()
    if err != nil {
        log.Fatal("dial target error:",err)
    }
    session,err := conn.NewSession()
    defer session.Close()
    if err != nil {
        log.Fatalf("session failed:%v",err)
    }
    defer session.Close()
    modes := ssh.TerminalModes{
        ssh.ECHO:          0,// disable echoing
        ssh.TTY_OP_ISPEED: 14400,// input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400,// output speed = 14.4kbaud
    }
    err = session.RequestPty("xterm",80,40,modes)
    if err != nil {
        log.Fatalf("Pty request failed:%v",err)
    }

    stdin,err = session.StdinPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdin for session: %v",err)
    }
    stdout,err = session.StdoutPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdout for session: %v",err)
    }
    stderr,err = session.StderrPipe()
    if err != nil {
        log.Fatalf("Unable to setup stderr for session: %v",err)
    }

    quit := make(chan bool)
    go func() {
        for {
            select {
            case <-quit:
                fmt.Println("exiting goroutine")
                return
            default:
                fmt.Println("go routine is running")
                io.Copy(os.Stdout,stdout)
                io.Copy(stdin,os.Stdin)
                io.Copy(os.Stderr,stderr)
            }
        }
    }()

    if err := session.Run(c); err != nil {
        log.Fatal("Failed to run: " + err.Error())
    }
    fmt.Println("closing the channel")
    close(quit)
    fmt.Println("the channel is closed")
}

func main() {
    options()
}

解决方法

首先,io.Copy是一个阻塞调用,它将io.writer的缓冲区复制到io.reader,直到在src上达到EOF或发生错误为止。 Here's来自golang的io模块的摘录:

// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil,one is allocated.

func copyBuffer(dst Writer,src Reader,buf []byte) (written int64,err error) {
    // If the reader has a WriteTo method,use it to do the copy.
    // Avoids an allocation and a copy.
    if wt,ok := src.(WriterTo); ok {
        return wt.WriteTo(dst)
    }
    // Similarly,if the writer has a ReadFrom method,use it to do the copy.
    if rt,ok := dst.(ReaderFrom); ok {
        return rt.ReadFrom(src)
    }
    if buf == nil {
        size := 32 * 1024
        if l,ok := src.(*LimitedReader); ok && int64(size) > l.N {
            if l.N < 1 {
                size = 1
            } else {
                size = int(l.N)
            }
        }
        buf = make([]byte,size)
    }
    for {
        nr,er := src.Read(buf)
        if nr > 0 {
            nw,ew := dst.Write(buf[0:nr])
            if nw > 0 {
                written += int64(nw)
            }
            if ew != nil {
                err = ew
                break
            }
            if nr != nw {
                err = ErrShortWrite
                break
            }
        }
        if er != nil {
            if er != EOF {
                err = er
            }
            break
        }
    }
    return written,err
}

现在您的问题了
当在第一次迭代中在会话的stdin和stderr上都进行io.copy调用时,它将等待,直到stderr和stdin上都存在要复制的消息并被阻止。 只需替换您的goroutine即可执行复制标准输出,即可在每次迭代中获得预期的行为。同样,stderr,stdin也需要由自己的goroutine管理:

go func() {
        for {
            io.Copy(os.Stdout,stdout)
        }
    }()

SigTerm将用于终止会话,存在一个称为interrupt的posix信号,可用于中断cmd。 Here's列出所有POSIX信号,表示使用的ssh库。

这是中断信号和预期行为的演示:

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "os"
    "time"

    "golang.org/x/crypto/ssh"
)

var (
    stdout,stderr io.Reader
    stdin          io.WriteCloser
    timeout        = 30 * time.Second
)

const (
    MYUSER = ""
    MYPASS = ""
)

func sshConfig() *ssh.ClientConfig {
    config := &ssh.ClientConfig{
        User: MYUSER,Auth: []ssh.AuthMethod{
            ssh.Password(MYPASS),},HostKeyCallback: ssh.HostKeyCallback(func(hostname string,remote net.Addr,key ssh.PublicKey) error { return nil }),Timeout:         timeout,}
    return config
}

func options() {
    menuString := `
  Enter 1 for "uptime"
  Enter 2 for "ping -c 20 www.google.com"
  Type exit to exit
  `
    for i := 0; i <= 5; i++ {
        var num,cmd string
        fmt.Println(menuString)
        if i == 0 {
            fmt.Print("Select an option from above: ")
        } else {
            fmt.Println("You'll have to press enter before entering your option. Why?")
            fmt.Print("Select an option from above: ")
        }

        fmt.Scanln(&num)
        switch num {
        case "1":
            cmd = "uptime"
            connect(cmd)
        case "2":
            cmd = "ping -c 20 www.google.com"
            connect(cmd)
        case "exit":
            fmt.Println("exiting")
            os.Exit(0)
        default:
            fmt.Println("Invalid response")
            fmt.Println("Select an option from above: ")
        }
    }
}

func connect(c string) {
    config := sshConfig()
    conn,err := ssh.Dial("tcp","127.0.0.1:22",config)
    defer conn.Close()
    if err != nil {
        log.Fatal("dial target error:",err)
    }
    session,err := conn.NewSession()
    defer session.Close()
    if err != nil {
        log.Println("session failed:%v",err)
    }
    defer session.Close()
    modes := ssh.TerminalModes{
        ssh.ECHO:          0,// disable echoing
        ssh.TTY_OP_ISPEED: 14400,// input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400,// output speed = 14.4kbaud
    }
    err = session.RequestPty("xterm",80,40,modes)
    if err != nil {
        log.Fatalf("Pty request failed:%v",err)
    }

    stdin,err = session.StdinPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdin for session: %v",err)
    }
    stdout,err = session.StdoutPipe()
    if err != nil {
        log.Fatalf("Unable to setup stdout for session: %v",err)
    }
    stderr,err = session.StderrPipe()
    if err != nil {
        log.Fatalf("Unable to setup stderr for session: %v",err)
    }

    go func() {
        for {
            io.Copy(os.Stdout,stdout)
        }
    }()

    c = "echo 'go is awesome' && sleep 10 && " + c
    go func() {
        time.Sleep(1 * time.Second)
        session.Signal(ssh.SIGINT)
    }()

    if err := session.Run(c); err != nil {
        log.Println("Failed to run: " + err.Error())
    }
    fmt.Println("closing the channel")
    fmt.Println("the channel is closed")
}

func main() {
    options()
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-