【问题标题】:golang scp file using crypto/ssh使用加密/ssh 的 golang scp 文件
【发布时间】:2016-11-09 14:27:38
【问题描述】:

我正在尝试通过 ssh 下载远程文件 以下方法在 shell 上运行良好

ssh hostname "tar cz /opt/local/folder" > folder.tar.gz

但是,在 golang 上使用相同的方法会在输出工件大小上产生一些差异。例如,具有纯 shell 的相同文件夹会生成工件 gz 文件 179B,并且与 go 脚本 178B 相同。 我假设 io.Reader 中遗漏了某些内容,或者会话提前关闭。请大家帮忙。

这是我的脚本示例:

func executeCmd(cmd, hostname string, config *ssh.ClientConfig, path string) error {
    conn, _ := ssh.Dial("tcp", hostname+":22", config)
    session, err := conn.NewSession()
    if err != nil {
        panic("Failed to create session: " + err.Error())
    }

    r, _ := session.StdoutPipe()
    scanner := bufio.NewScanner(r)

    go func() {
        defer session.Close()

        name := fmt.Sprintf("%s/backup_folder_%v.tar.gz", path, time.Now().Unix())
        file, err := os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
        if err != nil {
            panic(err)
        }
        defer file.Close()
        for scanner.Scan() {
            fmt.Println(scanner.Bytes())
            if err := scanner.Err(); err != nil {
                fmt.Println(err)
            }

            if _, err = file.Write(scanner.Bytes()); err != nil {
                log.Fatal(err)

            }
        }
    }()

    if err := session.Run(cmd); err != nil {
        fmt.Println(err.Error())
        panic("Failed to run: " + err.Error())
    }

    return nil
}

谢谢!

【问题讨论】:

  • 扫描仪用于换行符分隔的文本,不要将其用于二进制数据(它会删除换行符)
  • 谢谢 JimB,你能告诉我什么可以用于二进制数据吗?
  • 此外,使用比秒更独特的东西来创建唯一文件名(如 ioutil.TempFile 或随机字符串)也更安全,因为您的程序的 2 次快速运行或同时执行可能会发生冲突。

标签: go ssh scp


【解决方案1】:

您可以尝试这样做:

r, _ := session.StdoutPipe()
reader := bufio.NewReader(r)

go func() {
    defer session.Close()
    // open file etc

    // 10 is the number of bytes you'd like to copy in one write operation
    p := make([]byte, 10)
    for {
        n, err := reader.Read(p)
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal("err", err)
        }

        if _, err = file.Write(p[:n]); err != nil {
            log.Fatal(err)
        }
    }
}()

确保您的 goroutine 正确同步,以便将输出完全写入文件。

【讨论】:

  • 10 对于缓冲区来说不是一个有用的大小,它太小了,并且不符合 2 的任何幂。您还需要检查 before读取的字节数> 检查 EOF,以免无意中截断流。
  • 是的,它更像是一个占位符。不过,什么尺寸才合适?
  • io.Copy 默认使用 32k。只需使用 io.Copy (或 CopyBuffer 如果您需要不同的缓冲区大小),因为它会正确处理各种错误组合。
【解决方案2】:

bufio.Scanner 用于换行符分隔的文本。根据文档,扫描程序将删除换行符,从您的二进制文件中删除任何 10s。

您不需要 goroutine 来进行复制,因为您可以使用 session.Start 异步启动进程。

您可能也不需要使用 bufio。您应该使用 io.Copy 来复制文件,该文件在 ssh 客户端本身已经完成的任何缓冲之上已经有一个内部缓冲区。如果需要额外的缓冲区来提高性能,请将会话输出包装在 bufio.Reader

最后,你返回一个错误值,所以使用它而不是在常规错误条件下恐慌。

conn, err := ssh.Dial("tcp", hostname+":22", config)
if err != nil {
    return err
}

session, err := conn.NewSession()
if err != nil {
    return err
}
defer session.Close()

r, err := session.StdoutPipe()
if err != nil {
    return err
}

name := fmt.Sprintf("%s/backup_folder_%v.tar.gz", path, time.Now().Unix())
file, err := os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
    return err
}
defer file.Close()

if err := session.Start(cmd); err != nil {
    return err
}

n, err := io.Copy(file, r)
if err != nil {
    return err
}

if err := session.Wait(); err != nil {
    return err
}

return nil

【讨论】:

  • 感谢您的回复,对我帮助很大。
  • 只是一个关于 io.Copy 的快速问题。我使用 io.Reader 的原因是我需要传输每个 15-30Gb 的大型 tar 包,据我所知 io.Copy 会尝试将其保留在内存中,直到找到 EOF?对吗?
  • @neveragny:不,我不确定是什么给了你这样的印象。 io.Copy副本直接从src到dst。
  • 我刚刚运行了它,当 ssh 传输数据时,文件大小没有改变,所以因为它应该将数据保存在某个地方以便稍后写入,这个地方应该是 mem,不是吗?还是将其保存在 FS 上的某个缓存中? (这只是我好奇)thx
  • @neveragny:您可以查看io package source 并快速看到它读取然后立即写入。写入由操作系统缓冲,并且在文件同步之前不会写入磁盘,这可能是在文件关闭之前。
猜你喜欢
  • 2014-07-29
  • 2021-09-16
  • 1970-01-01
  • 2018-09-23
  • 2017-06-25
  • 2014-12-14
  • 2012-11-28
  • 2013-02-16
相关资源
最近更新 更多