Golang, multiple commands in the same SSH Session

huangapple go评论77阅读模式
英文:

Golang, multiple commands in the same SSH Session

问题

我需要在同一个SSH会话中运行多个命令。在每个命令之间,我需要解析输出。

我发现我可以从标准输出(STDOUT)中打印出第一行,但是当我尝试打印所有内容时,它就会卡住,似乎在等待STDOUT。这对于打印第一行是有效的,但我无法弄清楚如何打印所有来自STDOUT的内容,并释放回程序以运行下一个命令。

我需要运行一个命令,解析输出,然后在同一个会话中运行第二个命令。

以下是我的代码示例:

package main

import (
	"bufio"
	"fmt"
	"log"

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

func main() {
	// SSH凭证
	sshConfig := &ssh.ClientConfig{
		User: "rob",
		Auth: []ssh.AuthMethod{
			ssh.Password("OMGAPASSWORD....EEEEEEE"),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	// 连接到SSH服务器
	client, err := ssh.Dial("tcp", "*************:22", sshConfig)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	// 打开一个新会话
	session, err := client.NewSession()
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	// 设置输入和输出流
	sshIn, _ := session.StdinPipe()
	sshOut, _ := session.StdoutPipe()
	//sshErr, _ := session.StderrPipe()

	// 启动SSH会话
	if err := session.Start("/bin/bash"); err != nil {
		log.Fatalf("Failed to start SSH session: %v", err)
	}

	// 发送第一个命令
	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)
	}
}

希望对你有所帮助!

英文:

I need to run multiple commands in the same SSH Session. Between each command I'm sending. I need to parse the output.

I found I can printout the first line from STDOUT, but when I try and print everything it just hangs, seeming waiting for STDOUT. This works for printing the first line, I can't figure out how to print all from STDOUT and release back to the program to run the next command.

I need to run a command, parse the output, then run a second command within the same session.

Example of my code.

package main
import (
&quot;bufio&quot;
&quot;fmt&quot;
&quot;log&quot;
&quot;golang.org/x/crypto/ssh&quot;
)
func main() {
// SSH credentials
sshConfig := &amp;ssh.ClientConfig{
User: &quot;rob&quot;,
Auth: []ssh.AuthMethod{
ssh.Password(&quot;OMGAPASSWORD....EEEEEEE&quot;),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH server
client, err := ssh.Dial(&quot;tcp&quot;, &quot;*************:22&quot;, 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(&quot;/bin/bash&quot;); err != nil {
log.Fatalf(&quot;Failed to start SSH session: %v&quot;, err)
}
// Send the first command
cmd1 := &quot;ls -al&quot;
fmt.Fprintf(sshIn, &quot;%s\n&quot;, cmd1)
lines := make(chan string)
go func() {
scanner := bufio.NewScanner(sshOut)
for scanner.Scan() {
lines &lt;- scanner.Text()
}
close(lines)
}()
for line := range lines {
fmt.Println(line)
}
}

答案1

得分: 2

session.Start("/bin/bash")

尝试通过持久化的 shell 运行连续的命令是你所有困难的根源。由于你正在读取和写入 bash,你无法轻松地知道每个命令是否成功。

相反,每个命令都启动一个新的会话。将 stdout 和 stderr 复制到缓冲区,然后你可以对它们进行任何操作。

	for _, cmd := range []string{"ls -al", `bc <<< "3/2.0"`} {
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
var stdout, stderr bytes.Buffer
// 设置输入和输出流
//sshIn, _ := session.StdinPipe()
sshOut, _ := session.StdoutPipe()
sshErr, _ := session.StderrPipe()
go io.Copy(&stdout, sshOut)
go io.Copy(&stderr, sshErr)
// 启动命令
if err := session.Start(cmd); err != nil {
log.Fatalf("Failed to start SSH session: %v", err)
}
if err := session.Wait(); err != nil {
log.Fatal(fmt.Errorf("error waiting: %w", err))
}
session.Close()
fmt.Printf("Command: \t%s\nStdout: \t%s\nStderr:%s\n", cmd, stdout.String(), stderr.String())
}

这样你就知道每个连续命令何时结束以及它们的输出是什么。

如果你在 ~/.ssh 中有一个 ssh 密钥对,完整示例 应该在本地工作,但在 playground 中不起作用,因为没有 SSH 服务器可以连接(或密钥对)。可以像这样启动本地 ssh 服务器:

docker run --rm -d -e PUBLIC_KEY="$(cat ~/.ssh/id_rsa.pub)"  -e USER_NAME=$(whoami) -p 2222:2222 linuxserver/openssh-server

<details>
<summary>英文:</summary>

session.Start("/bin/bash")


Trying to run sequential commands through a persistent shell is the root of all your difficulties.  Since bash is what you&#39;re reading to and writing from, you can&#39;t easily tell when each command is successful. 
Instead, start a new session with each command.  Copy stdout and stderr to buffers, then you can do whatever you want with them.
for _, cmd := range []string{&quot;ls -al&quot;, `bc &lt;&lt;&lt;&quot;3/2.0&quot;`} {
session, err := client.NewSession()
if err != nil {
log.Fatal(err)
}
var stdout, stderr bytes.Buffer
// Set up input and output streams
//sshIn, _ := session.StdinPipe()
sshOut, _ := session.StdoutPipe()
sshErr, _ := session.StderrPipe()
go io.Copy(&amp;stdout, sshOut)
go io.Copy(&amp;stderr, sshErr)
// Start the command
if err := session.Start(cmd); err != nil {
log.Fatalf(&quot;Failed to start SSH session: %v&quot;, err)
}
if err := session.Wait(); err != nil {
log.Fatal(fmt.Errorf(&quot;error waiting: %w&quot;, err))
}
session.Close()
fmt.Printf(&quot;Command: \t%s\nStdout: \t%s\nStderr:%s\n&quot;, cmd, stdout.String(), stderr.String())
}

This way you know when each sequential command ends and whose output is which.  
[Full example][1] should work locally if you have an ssh key pair in `~/.ssh`, but doesn&#39;t work in playground because there&#39;s no SSh server to connect to (or key pair).  Start a local ssh server like this:

docker run --rm -d -e PUBLIC_KEY="$(cat ~/.ssh/id_rsa.pub)" -e USER_NAME=$(whoami) -p 2222:2222 linuxserver/openssh-server


[1]: https://go.dev/play/p/B2E4OD6uIie
</details>
# 答案2
**得分**: 1
主要缺少的是在最后的循环块中,当最终的循环被阻塞时,你需要再次从键盘读取,这样你就可以向`stdin`发送更多的命令。
类似这样的代码:
```go
// 你的代码的最后一个块
for {
fmt.Println("$")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
text := scanner.Text()
wr <- []byte(text + "\n")
}

另一个重要的部分缺失是保持从上面的操作系统扫描器接收命令并将其写入stdin的goroutine。

// 你需要创建一个goroutine来写入stdin
go func() {
	for {
		select {
		case d := <-wr:
			_, err := stdin.Write(d)
			if err != nil {
				fmt.Println(err.Error())
			}
		}
	}
}()

这两个代码块应该允许你连续输入命令。

基本上你需要:

  1. 一个写入SSH stdin的goroutine
  2. 一个从SSH stdout读取的goroutine
  3. 一个从SSH stderr读取的goroutine
  4. 一个循环来阻塞并从os.Stdin读取新的命令

我找到了一个功能性的代码片段的例子,你可以复制并运行它作为起点。

英文:

The main thing missing is that you need to read again from the keyboard when your final loop blocks so you can send more commands to stdin.

Something like this:

// Last block of your code
for {
	fmt.Println(&quot;$&quot;)

	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	text := scanner.Text()

	wr &lt;- []byte(text + &quot;\n&quot;)
}

Another important part missing is the goroutine that will keep receiving commands from the OS scanner above, and will write that to the stdin.

// You need to create a gorouting to write to stdin
go func() {
	for {
		select {
		case d := &lt;-wr:
			_, err := stdin.Write(d)
			if err != nil {
				fmt.Println(err.Error())
			}
		}
	}
}()

These two blocks should allow you to enter commands one after another.

Basically you'll need:

  1. goroutine that writes to SSH stdin
  2. goroutine that reads from SSH stdout
  3. goroutine that reads from SSH stderr
  4. A loop to block and read new commands from the os.Stdin

I've found this functional gist example that you can copy and run to use as a starting point.

huangapple
  • 本文由 发表于 2023年5月4日 00:45:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76166288.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定