# #go
Вопрос:
Я новичок в этом деле. Я начал писать свой первый код, в котором мне нужно загрузить кучу CSV-файлов с AWS. Я не понимаю, почему это дает мне следующую ошибку в режиме 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(amp;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(amp;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,
amp;s3.GetObjectInput{
Bucket: aws.String(Bucket),
Key: aws.String(fileToDownload),
})
if err != nil {
panic(err)
}
fmt.Println("Downloaded", file.Name(), numBytes, "bytes")
}
Ответ №1:
Во-первых, я не понимаю, зачем вам вообще нужен os.O_APPEND
флаг в первую очередь. Насколько я понимаю, вы можете опустить os.O_APPEND
.
Теперь давайте перейдем к реальной проблеме, почему это происходит:
Документ для O_APPEND
(Ссылка: 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
метод, т. е.,
Док для WriteAt
:
$ 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
поиск смещения могут создать проблемы, приводящие к неожиданным результатам, и это приведет к ошибкам.
Комментарии:
1. Огромное спасибо за объяснение. Действительно ценю это. Теперь мои проблемы в том, когда я опускаю » О. Добавить». я получаю вывод последнего csv-файла, который находится в aws. я хочу загрузить все csv-файлы…
Ответ №2:
Рассмотрим определение s3manager.Downloader
:
func (d Downloader) Download(w io.WriterAt, input *s3.GetObjectInput, options ...func(*Downloader)) (n int64, err error)
Первый аргумент-an io.WriterAt
; этот интерфейс является:
type WriterAt interface {
WriteAt(p []byte, off int64) (n int, err error)
}
Это означает, что Download
функция будет вызывать WriteAt
метод в том File
виде, в каком вы его передаете. В соответствии с документацией для File.WriteAt
Если файл был открыт с флагом O_APPEND, WriteAt возвращает ошибку.
Таким образом, это объясняет, почему вы получаете ошибку, но возникает вопрос «почему Download
используется WriteAt
и не принимается io.Writer
(и вызывается Write
)?»; ответ можно найти в документации:
The w io.WriterAt может быть удовлетворен операционной системой.Файл для одновременной загрузки из нескольких частей или в байтовой оболочке памяти []с использованием aws.буфер записи
Поэтому, чтобы повысить производительность, Downloader
можно сделать несколько одновременных запросов для частей файла, а затем записать их по мере их получения (это означает, что он может не записывать данные по порядку). Это также объясняет, почему многократный вызов функции с одинаковыми File
результатами приводит к перезаписи данных (при Downloader
извлечении каждого фрагмента файла он записывает его в соответствующей позиции в выходном файле; это перезаписывает все уже имеющиеся данные).
Приведенная выше цитата из документации также указывает на возможное решение; используйте aws.WriteAtBuffer
и, как только загрузка будет завершена, запишите данные в свой файл (который затем можно открыть с O_APPEND
помощью ) — что-то вроде этого:
buf := aws.NewWriteAtBuffer([]byte{})
numBytes, err := downloader.Download(buf,
amp;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)
}
Альтернативой может быть загрузка во временный файл, а затем добавление его в выходной файл (возможно, вам потребуется это сделать, если файлы большие).