Golang中ssh.Session.StdoutPipe()实现的疑问

Golang中ssh.Session.StdoutPipe()实现的疑问 正如描述所说,它返回管道的读取端。然而,我原本期望它会将写入端分配给 sesssion.Stdout。假设我想将标准错误重定向到同一个管道,以便供同一个读取器使用。 例如:sesssion.Stderr = session.Stdout。但在调用 StdoutPipe() 之后,sesssion.Stdout 仍然为 nil。 我是不是遗漏了什么?

// StdoutPipe returns a pipe that will be connected to the
// remote command's standard output when the command starts.
// There is a fixed amount of buffering that is shared between
// stdout and stderr streams. If the StdoutPipe reader is
// not serviced fast enough it may eventually cause the
// remote command to block.
func (s *Session) StdoutPipe() (io.Reader, error) {
	if s.Stdout != nil {
		return nil, errors.New("ssh: Stdout already set")
	}
	if s.started {
		return nil, errors.New("ssh: StdoutPipe after process started")
	}
	s.stdoutpipe = true
	return s.ch, nil
}

更多关于Golang中ssh.Session.StdoutPipe()实现的疑问的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

因为它的用法并非你所想的那样,其接口设计与 exec 非常相似。 如果你想同时输出标准错误和标准输出:

func test() {
	config := &ssh.ClientConfig{} // some config
	c, _ := ssh.Dial("tcp", "127.0.0.1:22", config)
	session, _ := c.NewSession()
	// 方案 a
	out1, _ := session.StdoutPipe()
	err1, _ := session.StderrPipe()
	reader := io.MultiReader(out1, err1)
	// todo return reader
	// 方案 b
	pipeR, pipeW := io.Pipe()
	session.Stdout = pipeW
	session.Stderr = pipeW
	// todo return pipeR

	// do session.Run or other ...
}

更多关于Golang中ssh.Session.StdoutPipe()实现的疑问的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


从代码实现来看,StdoutPipe() 方法确实不会设置 session.Stdout 字段。它只是将 s.stdoutpipe 标记为 true,并返回共享通道 s.ch 作为读取端。

当你需要同时捕获 stdout 和 stderr 到同一个管道时,正确的做法是:

  1. 先调用 StdoutPipe() 获取读取器
  2. session.Stderr 设置为 session.Stdout(但这里需要特殊处理)

实际上,你需要创建一个自定义的 io.Writer 来同时处理两个流。以下是示例代码:

package main

import (
    "bytes"
    "fmt"
    "io"
    "log"
    "golang.org/x/crypto/ssh"
)

func main() {
    // SSH连接配置
    config := &ssh.ClientConfig{
        User: "username",
        Auth: []ssh.AuthMethod{
            ssh.Password("password"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

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

    // 创建会话
    session, err := client.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    // 获取stdout管道
    stdout, err := session.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    // 创建缓冲区来合并输出
    var combinedOutput bytes.Buffer

    // 启动goroutine读取stdout
    go func() {
        io.Copy(&combinedOutput, stdout)
    }()

    // 设置stderr到同一个缓冲区
    session.Stderr = &combinedOutput

    // 执行远程命令
    err = session.Run("ls -la /tmp")
    if err != nil {
        log.Fatal(err)
    }

    // 输出合并的结果
    fmt.Println("Combined output:")
    fmt.Println(combinedOutput.String())
}

如果你想要更精确的控制,可以使用 io.MultiWriter

package main

import (
    "bytes"
    "fmt"
    "io"
    "log"
    "os"
    "golang.org/x/crypto/ssh"
)

func main() {
    config := &ssh.ClientConfig{
        User: "username",
        Auth: []ssh.AuthMethod{
            ssh.Password("password"),
        },
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    client, err := ssh.Dial("tcp", "host:22", config)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    session, err := client.NewSession()
    if err != nil {
        log.Fatal(err)
    }
    defer session.Close()

    // 创建管道
    stdout, err := session.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    // 创建自定义writer来合并输出
    type combinedWriter struct {
        buf bytes.Buffer
    }

    func (cw *combinedWriter) Write(p []byte) (n int, err error) {
        // 这里可以添加逻辑来区分stdout和stderr
        return cw.buf.Write(p)
    }

    cw := &combinedWriter{}

    // 使用io.MultiWriter将stderr重定向到同一个writer
    session.Stderr = cw

    // 启动goroutine读取stdout并写入合并的writer
    go func() {
        io.Copy(cw, stdout)
    }()

    err = session.Run("ls -la /tmp && echo 'error' >&2")
    if err != nil {
        // 注意:命令出错时,错误信息会在stderr中
        fmt.Printf("Command error: %v\n", err)
    }

    fmt.Println("Output:", cw.buf.String())
}

关键点:

  • StdoutPipe() 只返回读取端,不设置 session.Stdout
  • 你需要手动处理两个流的合并
  • 使用 io.Copy 或自定义 io.Writer 来合并输出
  • 确保在调用 Run()Start() 之前设置好所有的管道和重定向
回到顶部