问题描述
我有两个需要帮助的问题。我想我已经将其限制在goroutine中使用io.Copy了。行为就像需要使用Enter键“重新激活” stdin。
-
在循环的第一次迭代中,所有操作均按预期进行。它登录到localhost,运行命令,goroutine打印stdout和stderr。但是在随后的迭代中,我必须首先按Enter键以“退出goroutine”,然后在提示符下输入响应。我怀疑这与os.Stdin有关。有人可以提供一些建议吗?
-
是否可以在不中断脚本的情况下向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()
}