我是围棋新手。我开始写我的第一个代码,我必须从AWS下载一堆CSV文件。我不明白为什么它给我下面的错误与O_APPEND模式。如果我删除os.O_APPEND
,我只得到最后一个文件数据,这不是目标。
目标是将所有CSV文件下载到本地一个文件中。我想知道我做错了什么。
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
const (
AccessKeyId = "xxxxxxxxx"
SecretAccessKey = "xxxxxxxxxxxxxxxxxxxx"
Region = "eu-central-1"
Bucket = "dexter-reports"
bucketKey = "Jenkins/pluginVersions/"
)
func main() {
// Load the Shared AWS Configuration
os.Setenv("AWS_ACCESS_KEY_ID", AccessKeyId)
os.Setenv("AWS_SECRET_ACCESS_KEY", SecretAccessKey)
filename := "JenkinsPluginDetais.txt"
cred := credentials.NewStaticCredentials(AccessKeyId, SecretAccessKey, "")
config := aws.Config{Credentials: cred, Region: aws.String(Region), Endpoint: aws.String("s3.amazonaws.com")}
file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer file.Close()
sess, err := session.NewSession(&config)
if err != nil {
fmt.Println(err)
}
//list Buckets
ObjectList := listBucketObjects(sess)
//loop over the obectlist. First initialize the s3 downloader via s3manager
downloader := s3manager.NewDownloader(sess)
for _, item := range ObjectList.Contents {
csvFile := filepath.Base(*item.Key)
if csvFile != "pluginVersions" {
downloadBucketObjects(downloader, file, csvFile)
}
}
}
func listBucketObjects(sess *session.Session) *s3.ListObjectsV2Output {
//create a new s3 client
svc := s3.New(sess)
resp, err := svc.ListObjectsV2(&s3.ListObjectsV2Input{
Bucket: aws.String(Bucket),
Prefix: aws.String(bucketKey),
})
if err != nil {
panic(err)
}
return resp
}
func downloadBucketObjects(downloader *s3manager.Downloader, file *os.File, keyobj string) {
fileToDownload := bucketKey + keyobj
numBytes, err := downloader.Download(file,
&s3.GetObjectInput{
Bucket: aws.String(Bucket),
Key: aws.String(fileToDownload),
})
if err != nil {
panic(err)
}
fmt.Println("Downloaded", file.Name(), numBytes, "bytes")
}
首先,我不明白为什么你一开始就需要os.O_APPEND
标志。根据我的理解,你可以省略os.O_APPEND
。
现在,让我们来看看为什么会发生这种情况:
Doc forO_APPEND
(Ref: https://man7.org/linux/man-pages/man2/open.2.html):
O_APPEND
The file is opened in append mode. Before each write(2),
the file offset is positioned at the end of the file, as
if with lseek(2). The modification of the file offset and
the write operation are performed as a single atomic step.
所以对于每次调用write
,文件偏移量定位在文件的末尾。
但(*s3Manager.Download).Download
应该使用WriteAt
方法,即
Doc forWriteAt
:
$ go doc os WriteAt
package os // import "os"
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
WriteAt writes len(b) bytes to the File starting at byte offset off. It
returns the number of bytes written and an error, if any. WriteAt returns a
non-nil error when n != len(b).
If file was opened with the O_APPEND flag, WriteAt returns an error.
注意最后一行,如果文件以O_APPEND
标志打开,它将导致一个错误,它甚至是正确的,因为WriteAt的第二个参数是一个偏移量,但是混合O_APPEND
的行为和WriteAt
的偏移量查找可能会产生问题,导致意想不到的结果,它出错了。
考虑s3manager.Downloader
的定义:
func (d Downloader) Download(w io.WriterAt, input *s3.GetObjectInput, options ...func(*Downloader)) (n int64, err error)
第一个参数是io.WriterAt
;这个接口是:
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
这意味着Download
函数将调用你传递给它的File
中的WriteAt
方法。根据File.WriteAt
如果文件是以O_APPEND标志打开的,WriteAt返回一个错误。
所以这就解释了为什么你得到错误,但提出了问题"为什么Download
使用WriteAt
而不接受io.Writer
(并调用Write
)?";答案可以在文档中找到:
世界。操作系统可以满足WriterAt。文件做多部分并发下载,或在内存[]字节包装使用aws。WriteAtBuffer
因此,为了提高性能,Downloader
可能会对文件的某些部分同时发出多个请求,然后在接收到这些请求时将它们写出来(这意味着它可能不会按顺序写数据)。这也解释了为什么使用相同的File
多次调用该函数会导致覆盖数据(当Downloader
检索文件的每个块时,它会在输出文件的适当位置将其写出来;
以上引用的文档也指出了一个可能的解决方案;使用aws.WriteAtBuffer
,一旦下载完成,将数据写入文件(然后可以使用O_APPEND
打开)—如下所示:
buf := aws.NewWriteAtBuffer([]byte{})
numBytes, err := downloader.Download(buf,
&s3.GetObjectInput{
Bucket: aws.String(Bucket),
Key: aws.String(fileToDownload),
})
if err != nil {
panic(err)
}
_, err = file.Write(buf.Bytes())
if err != nil {
panic(err)
}
另一种方法是下载到一个临时文件中,然后将其附加到输出文件中(如果文件很大,可能需要这样做)。