使用crypto / ssh的golang scp文件

I'm trying to download a remote file over ssh The following approach works fine on shell

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

However the same approach on golang giving some difference in output artifact size. For example the same folders with pure shell produce artifact gz file 179B and same with go script 178B. I assume that something has been missed from io.Reader or session got closed earlier. Kindly ask you guys to help.

Here is the example of my script:

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
}

Thanks!

dongshengli6384
dongshengli6384 此外,使用比秒更唯一的东西来创建唯一的文件名(例如ioutil.TempFile或随机字符串)也更安全,因为程序的两次快速运行或同时执行可能会冲突。
大约 4 年之前 回复
duanfu3634
duanfu3634 感谢JimB,请问您可以建议将哪些用于二进制数据?
大约 4 年之前 回复
douchan7552
douchan7552 扫描仪用于换行符分隔的文本,请勿将其用于二进制数据(它会删除换行符)
大约 4 年之前 回复

2个回答



bufio.Scanner </ code>用于换行符分隔的文本。 根据文档,扫描程序将删除换行符,并从二进制文件中删除所有 10 </ code>。</ p>

您不需要goroutine即可 复制,因为您可以使用 session.Start </ code>异步启动该过程。 </ p>

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

最后,您返回一个错误值,因此请使用它,而不是在常规错误情况下惊慌。 </ p>

  conn,err:= ssh.Dial(“ tcp”,主机名+“:22”,配置)
if err!= nil {
return err
} \ n
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 。开始(CMD); err!= nil {
return err
}

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

if err:= session 。等待(); err!= nil {
return err
}

return nil
</ code> </ pre>
</ div>

展开原文

原文

bufio.Scanner is for newline delimited text. According to the documentation, the scanner will remove the newline characters, stripping any 10s out of your binary file.

You don't need a goroutine to do the copy, because you can use session.Start to start the process asynchronously.

You probably don't need to use bufio either. You should be using io.Copy to copy the file, which has an internal buffer already on top of any buffering already done in the ssh client itself. If an additional buffer is needed for performance, wrap the session output in a bufio.Reader

Finally, you return an error value, so use it rather than panic'ing on regular error conditions.

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

dongshang1529
dongshang1529 您可以查看io包的源代码,并迅速看到它先读取然后立即写入。 写入内容由操作系统缓冲,直到文件同步后才写入磁盘,直到文件关闭才可能写入磁盘。
大约 4 年之前 回复
doujing1858
doujing1858 我只是运行它,而在ssh传输数据时,文件大小并没有改变,因此,由于它应该将数据保留在某个地方以便以后写入,所以这个地方应该是mem,不是吗? 还是将其保存在FS上的某些缓存中? (那只是我好奇)
大约 4 年之前 回复
doushoubu5360
doushoubu5360 不,我不确定是什么让您有这种印象。 io。直接将副本从src复制到dst。
大约 4 年之前 回复
dqg2269
dqg2269 关于io.Copy的一个简短问题。 我使用io.Reader的原因是,我需要传输每个15-30Gb的大型tar包,据我所知,io.Copy会尝试将其保留在mem中,直到找到EOF为止。 那正确吗?
大约 4 年之前 回复
dpruwm6206
dpruwm6206 感谢您的答复,它对我有很大帮助。
大约 4 年之前 回复



您可以尝试执行以下操作:</ p>

  r,_:=会话 .stdoutPipe()
reader:= bufio.NewReader(r)

go func(){
延迟会话.Close()
//打开文件等

// 10是您要读取的字节数 想要在一次写入操作中进行复制
p:= make([[byte,10)
for {
n,err:= reader.Read(p)
if err == io.EOF {
break

}
if err!= nil {
log.Fatal(“ err”,err)
}

如果_,err = file.Write(p [:n]); err!= nil {
log.Fatal(err)
}
}
}()
</ code> </ pre>

请确保您的goroutine已正确同步,以便输出 已完全写入文件。</ p>
</ div>

展开原文

原文

You can try doing something like this:

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)
        }
    }
}()

Make sure your goroutines are synchronized properly so output is completeky written to the file.

drd43058
drd43058 io.Copy默认使用32k。 只需使用io.Copy(如果需要不同的缓冲区大小,则使用CopyBuffer),因为它将正确处理各种错误组合。
大约 4 年之前 回复
dongzaijiao4863
dongzaijiao4863 是的,它更像是一个占位符。 不过什么尺寸合适?
大约 4 年之前 回复
dongquan8753
dongquan8753 10对于缓冲区不是一个有用的大小,它太小了,并且与2的次幂不对齐。您还需要在检查EOF之前检查来回读取的字节,以免意外截断流。
大约 4 年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐