Golang中同一SSH Session执行多命令并解析输出的问题
问题描述
我需要在同一个SSH会话中执行多条命令,每条命令执行后解析其输出。目前我只能打印出STDOUT的第一行,但尝试打印全部输出时程序会挂起,似乎在等待STDOUT。我希望实现执行一条命令、解析输出后,在同一会话中执行第二条命令。以下是我的示例代码:
package main import ( "bufio" "fmt" "log" "golang.org/x/crypto/ssh" ) func main() { // SSH credentials sshConfig := &ssh.ClientConfig{ User: "rob", Auth: []ssh.AuthMethod{ ssh.Password("OMGAPASSWORD....EEEEEEE"), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // Connect to SSH server client, err := ssh.Dial("tcp", "*************:22", sshConfig) if err != nil { log.Fatal(err) } defer client.Close() // Open a new session session, err := client.NewSession() if err != nil { log.Fatal(err) } defer session.Close() // Set up input and output streams sshIn, _ := session.StdinPipe() sshOut, _ := session.StdoutPipe() //sshErr, _ := session.StderrPipe() // Start the SSH session if err := session.Start("/bin/bash"); err != nil { log.Fatalf("Failed to start SSH session: %v", err) } // Send the first command cmd1 := "ls -al" fmt.Fprintf(sshIn, "%s\n", cmd1) lines := make(chan string) go func() { scanner := bufio.NewScanner(sshOut) for scanner.Scan() { lines <- scanner.Text() } close(lines) }() for line := range lines { fmt.Println(line) } }
问题原因
原代码挂起的核心问题是:启动/bin/bash后,执行完ls -al命令,bash不会主动关闭标准输出,而是会一直等待新的命令输入。你的scanner会持续监听stdout,永远不会触发scanner.Scan()返回false的情况,导致程序阻塞在读取循环中。
同时,代码没有明确的标记来判断一条命令是否执行完毕,无法准确切换到下一条命令的执行。
解决方案
通过设置自定义提示符来标记命令执行的边界,这样就能准确识别每条命令的输出结束点,避免无限等待。同时分别处理标准输出和错误输出,完善会话的生命周期管理。
修改后的代码如下:
package main import ( "bufio" "fmt" "log" "strings" "golang.org/x/crypto/ssh" ) func main() { // SSH credentials sshConfig := &ssh.ClientConfig{ User: "rob", Auth: []ssh.AuthMethod{ ssh.Password("OMGAPASSWORD....EEEEEEE"), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // Connect to SSH server client, err := ssh.Dial("tcp", "*************:22", sshConfig) if err != nil { log.Fatal(err) } defer client.Close() // Open a new session session, err := client.NewSession() if err != nil { log.Fatal(err) } defer session.Close() // Set up input and output streams sshIn, err := session.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } sshOut, err := session.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) } sshErr, err := session.StderrPipe() if err != nil { log.Fatalf("Failed to get stderr pipe: %v", err) } // 设置独特的自定义提示符,用于识别命令执行结束 customPrompt := "CMD_DONE>>>" setupPromptCmd := fmt.Sprintf("PS1='%s'\n", customPrompt) // Start the SSH session if err := session.Start("/bin/bash"); err != nil { log.Fatalf("Failed to start SSH session: %v", err) } // 后台监听错误输出 go func() { scanner := bufio.NewScanner(sshErr) for scanner.Scan() { fmt.Printf("[STDERR] %s\n", scanner.Text()) } }() scanner := bufio.NewScanner(sshOut) // 读取初始bash输出,直到出现默认提示符 for scanner.Scan() { if strings.HasSuffix(scanner.Text(), "$") || strings.HasSuffix(scanner.Text(), "#") { break } } // 发送自定义提示符设置命令,读取到新提示符出现 fmt.Fprint(sshIn, setupPromptCmd) for scanner.Scan() { if strings.HasSuffix(scanner.Text(), customPrompt) { break } } // 执行第一条命令并读取输出 cmd1 := "ls -al" fmt.Printf("\n=== Executing: %s ===\n", cmd1) fmt.Fprintf(sshIn, "%s\n", cmd1) var cmd1Output []string for scanner.Scan() { line := scanner.Text() if strings.HasSuffix(line, customPrompt) { break } cmd1Output = append(cmd1Output, line) } // 解析第一条命令输出 fmt.Println("Command 1 Output:") for _, line := range cmd1Output { fmt.Println(line) } // 执行第二条命令并读取输出 cmd2 := "pwd" fmt.Printf("\n=== Executing: %s ===\n", cmd2) fmt.Fprintf(sshIn, "%s\n", cmd2) var cmd2Output []string for scanner.Scan() { line := scanner.Text() if strings.HasSuffix(line, customPrompt) { break } cmd2Output = append(cmd2Output, line) } // 解析第二条命令输出 fmt.Println("Command 2 Output:") for _, line := range cmd2Output { fmt.Println(line) } // 退出bash并等待会话结束 fmt.Fprint(sshIn, "exit\n") if err := session.Wait(); err != nil { log.Printf("Session exit error: %v", err) } }
关键改动说明
- 自定义提示符:通过
PS1设置独特的提示符CMD_DONE>>>,作为命令执行完成的标记,解决了无法判断输出边界的问题。 - 错误输出处理:新增goroutine监听stderr,避免错误信息被忽略。
- 会话生命周期管理:最后发送
exit命令并调用session.Wait(),确保会话正常结束。 - 分阶段读取输出:每条命令执行后,读取输出直到自定义提示符出现,确保获取完整的命令输出后再执行下一条命令。
内容的提问来源于stack exchange,提问作者Robert YT
相关产品推荐
相关产品推荐

