Node.js& Amazon S3:如何遍历存储桶中的所有文件?

时间:2012-02-24 20:28:38

标签: node.js amazon-s3

Node.js是否有任何Amazon S3客户端库允许列出S3存储桶中的所有文件?

最知名的aws2jsknox似乎没有此功能。

15 个答案:

答案 0 :(得分:57)

使用官方aws-sdk

var allKeys = [];
function listAllKeys(marker, cb)
{
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.push(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextMarker, cb);
    else
      cb();
  });
}

请参阅s3.listObjects

编辑2017 : 同样的基本想法,但现在建议使用listObjectsV2( ... )并使用ContinuationToken(请参阅s3.listObjectsV2):

var allKeys = [];
function listAllKeys(token, cb)
{
  var opts = { Bucket: s3bucket };
  if(token) opts.ContinuationToken = token;

  s3.listObjectsV2(opts, function(err, data){
    allKeys = allKeys.concat(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextContinuationToken, cb);
    else
      cb();
  });
}

答案 1 :(得分:15)

这是我编写的用于从截断列表中组装S3对象的Node代码。

var params = {
    Bucket: <yourbucket>,
    Prefix: <yourprefix>,
};

var s3DataContents = [];    // Single array of all combined S3 data.Contents

function s3Print() {
    if (program.al) {
        // --al: Print all objects
        console.log(JSON.stringify(s3DataContents, null, "    "));
    } else {
        // --b: Print key only, otherwise also print index 
        var i;
        for (i = 0; i < s3DataContents.length; i++) {
            var head = !program.b ? (i+1) + ': ' : '';
            console.log(head + s3DataContents[i].Key);
        }
    }
}

function s3ListObjects(params, cb) {
    s3.listObjects(params, function(err, data) {
        if (err) {
            console.log("listS3Objects Error:", err);
        } else {
            var contents = data.Contents;
            s3DataContents = s3DataContents.concat(contents);
            if (data.IsTruncated) {
                // Set Marker to last returned key
                params.Marker = contents[contents.length-1].Key;
                s3ListObjects(params, cb);
            } else {
                cb();
            }
        }
    });
}

s3ListObjects(params, s3Print);

请注意NextMarker的listObject's文档, NOT 始终存在于返回的数据对象中,所以我在上面的代码中根本不使用它... < / p>

  

NextMarker - (String)截断响应时( IsTruncated   响应中的元素值为true),您可以使用键名   此字段作为后续请求中的标记来获取下一组   对象。 Amazon S3按字母顺序列出对象注意:这   仅当您具有分隔符请求参数时才返回元素   指定。如果响应不包含NextMarker,那就是   截断后,您可以 使用响应中最后一个Key的值   获取下一组对象的后续请求中的标记   键

整个程序现已推到https://github.com/kenklin/s3list

答案 2 :(得分:8)

实际上,aws2js支持通过s3.get()方法调用在低级别的存储桶中列出对象。要做到这一点,必须传递Amazon S3 REST API page上记录的prefix参数:

var s3 = require('aws2js').load('s3', awsAccessKeyId, awsSecretAccessKey);    
s3.setBucket(bucketName);

var folder = encodeURI('some/path/to/S3/folder');
var url = '?prefix=' + folder;

s3.get(url, 'xml', function (error, data) {
    console.log(error);
    console.log(data);
});

上述代码段中的data变量包含bucketName存储桶中所有对象的列表。

答案 3 :(得分:5)

发布 knox-copy ,当我找不到一个好的现有解决方案时。将Rest API的所有分页细节包含在熟悉的节点流中:

var knoxCopy = require('knox-copy');

var client = knoxCopy.createClient({
  key: '<api-key-here>',
  secret: '<secret-here>',
  bucket: 'mrbucket'
});

client.streamKeys({
  // omit the prefix to list the whole bucket
  prefix: 'buckets/of/fun' 
}).on('data', function(key) {
  console.log(key);
});

如果您列出的文件少于1000个,则单个页面将起作用:

client.listPageOfKeys({
  prefix: 'smaller/bucket/o/fun'
}, function(err, page) {
  console.log(page.Contents); // <- Here's your list of files
});

答案 4 :(得分:3)

我将此版本与async/await一起使用。
此函数将以数组形式返回内容。
我还使用了NextContinuationToken而不是标记。

async function getFilesRecursivelySub(param) {

    // Call the function to get list of items from S3.
    let result = await s3.listObjectsV2(param).promise();

    if(!result.IsTruncated) {
        // Recursive terminating condition.
        return result.Contents;
    } else {
        // Recurse it if results are truncated.
        param.ContinuationToken = result.NextContinuationToken;
        return result.Contents.concat(await getFilesRecursivelySub(param));
    }
}

async function getFilesRecursively() {

    let param = {
        Bucket: 'YOUR_BUCKET_NAME'
        // Can add more parameters here.
    };

    return await getFilesRecursivelySub(param);
}

答案 5 :(得分:2)

这是一个老问题,我想AWS JS SDK自被问及以来已发生了很大变化。现在,这是另一种方法:

s3.listObjects({Bucket:'mybucket', Prefix:'some-pfx'}).
on('success', function handlePage(r) {
    //... handle page of contents r.data.Contents

    if(r.hasNextPage()) {
        // There's another page; handle it
        r.nextPage().on('success', handlePage).send();
    } else {
        // Finished!
    }
}).
on('error', function(r) {
    // Error!
}).
send();

答案 6 :(得分:2)

Meekohi提供了一个非常好的答案,但(新)文档指出NextMarker可以是未定义的。在这种情况下,您应该使用最后一个键作为标记。

所以他的代码示例可以改为:

var allKeys = [];
function listAllKeys(marker, cb) {
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.push(data.Contents);
    if(data.IsTruncated)
      listAllKeys(data.NextMarker || data.Contents[data.Contents.length-1].Key, cb);
    else
      cb();
  });
}

由于我没有所需的声誉,因此无法对原始答案发表评论。对于糟糕的加价道歉。

答案 7 :(得分:2)

使用异步生成器

const { S3 } = require('aws-sdk');
const s3 = new S3();

async function* listAllKeys(opts) {
    opts = {...opts};
    do {
        const data = await s3.listObjectsV2(opts).promise();
        opts.ContinuationToken = data.NextContinuationToken;
        yield data;
    } while (opts.ContinuationToken)
}

const opts = {
    Bucket: 'bucket-xyz',
    /* required */
    // ContinuationToken: 'STRING_VALUE',
    // Delimiter: 'STRING_VALUE',
    // EncodingType: url,
    // FetchOwner: true || false,
    // MaxKeys: 'NUMBER_VALUE',
    // Prefix: 'STRING_VALUE',
    // RequestPayer: requester,
    // StartAfter: 'STRING_VALUE'
};

async function main() {
    // using for of await loop
    for await (const data of listAllKeys(opts)) {
        console.log(data.Contents)
    }

    // or lazy-load
    const keys = listAllKeys(opts);
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: undefined, done: true}
}
main();

// Making Observable

const lister = opts => o => {
    let needMore = true;
    (async () => {
        const keys = listAllKeys(opts);
        for await (const data of keys) {
            if (data.done) break;
            o.next(data);
            if (!needMore) break;
        }
        o.complete();
    })();
    return () => (needMore = false);
}

// Using Rxjs

const { Observable } = require('rxjs');
const { flatMap } = require('rxjs/operators')

function listAll() {
    return Observable.create(lister(opts))
        .pipe(flatMap(v => v.Contents))
        .subscribe(console.log);
}

listAll();


// Using Nodejs EventEmitter

const EventEmitter = require('events');

const _eve = new EventEmitter();
_eve.on('next', console.log);

const stop = lister(opts)({
    next: v => _eve.emit('next', v),
    error: e => _eve.emit('error', e),
    complete: v => _eve.emit('complete', v)
});

答案 8 :(得分:1)

如果您只想获取S3 Bucket内特定文件夹中的键列表,那么这将非常有用。

基本上,listObjects功能会从我们设置的Marker开始搜索,并会搜索maxKeys: 1000作为限制。所以它将逐个搜索文件夹,并从存储桶中的不同文件夹中找到它的前1000个密钥。

考虑我的存储桶中有许多文件夹,前缀为prod/some date/, Ex: prod/2017/05/12/ ,prod/2017/05/13/,etc

我想仅在prod/2017/05/12/文件夹中获取对象列表(文件名),然后我将prod/2017/05/12/指定为我的开始,并prod/2017/05/13/ [您的下一个文件夹名称]作为我的结束和在代码中我遇到了结束时打破了循环。

Key中的每个data.Contents都会如下所示。

{      Key: 'prod/2017/05/13/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
       LastModified: 2017-05-13T00:59:02.000Z,
       ETag: '"630b2sdfsdfs49ef392bcc16c833004f94ae850"',
       Size: 134236366,
       StorageClass: 'STANDARD',
       Owner: { } 
 }

代码:

var list = [];

function listAllKeys(s3bucket, start, end) {
  s3.listObjects({
    Bucket: s3bucket,
    Marker: start,
    MaxKeys: 1000,
  }, function(err, data) {
      if (data.Contents) {
        for (var i = 0; i < data.Contents.length; i++) {
         var key = data.Contents[i].Key;    //See above code for the structure of data.Contents
          if (key.substring(0, 19) != end) {
             list.push(key);
          } else {
             break;   // break the loop if end arrived
          }
       }
        console.log(list);
        console.log('Total - ', list.length);      
     }
   });
 }

listAllKeys('BucketName', 'prod/2017/05/12/', 'prod/2017/05/13/');

输出:

[ 'prod/2017/05/12/05/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
  'prod/2017/05/12/05/a36528b9-e071-4b83-a7e6-9b32d6bce6d8.jpg',
  'prod/2017/05/12/05/bc4d6d4b-4455-48b3-a548-7a714c489060.jpg',
  'prod/2017/05/12/05/f4b8d599-80d0-46fa-a996-e73b8fd0cd6d.jpg',
  ... 689 more items ]
Total - 692

答案 9 :(得分:1)

我最终围绕ListObjectsV2构建了一个包装器函数,以相同的方式工作并且采用了相同的参数,但是递归地工作直到IsTruncated = false并返回在回调函数的第二个参数中找到的所有键作为数组。

const AWS = require('aws-sdk')
const s3 = new AWS.S3()

function listAllKeys(params, cb)
{
   var keys = []
   if(params.data){
      keys = keys.concat(params.data)
   }
   delete params['data']

   s3.listObjectsV2(params, function(err, data){
     if(err){
       cb(err)
     } else if (data.IsTruncated) {
       params['ContinuationToken'] = data.NextContinuationToken
       params['data'] = data.Contents
       listAllKeys(params, cb)
     } else {
       keys = keys.concat(data.Contents)
       cb(null,keys)
     }
   })
}

答案 10 :(得分:0)

尽管@ Meekohi的答案在技术上有效,但我对AWS SDK for NodeJS的S3部分感到非常痛苦。在完成之前的所有模块,例如aws-sdks3knox之后,我决定通过操作系统软件包管理器安装s3cmd并使用{{shell-out ... 3}}

类似的东西:

    var s3cmd = new cmd_exec('s3cmd', ['ls', filepath, 's3://'+inputBucket],
            function (me, data) {me.stdout += data.toString();},
            function (me) {me.exit = 1;}
    );
    response.send(s3cmd.stdout);

(使用child_process中的cmd_exec实施)

这种方法效果很好 - 包括文件上传等其他问题。

答案 11 :(得分:0)

这是我根据其他答案得出的结果。
您可以await listAllKeys(),而不必使用回调。

const listAllKeys = () =>
  new Promise((resolve, reject) => {
    let allKeys = [];
    const list = marker => {
      s3.listObjects({ Marker: marker }, (err, data) => {
        if (err) {
          reject(err);
        } else if (data.IsTruncated) {
          allKeys.push(data.Contents);
          list(data.NextMarker || data.Contents[data.Contents.length - 1].Key);
        } else {
          allKeys.push(data.Contents);
          resolve(allKeys);
        }
      });
    };
    list();
  });

这假设您已经像这样初始化了s3变量

const s3 = new aws.S3({
  apiVersion: API_VERSION,
  params: { Bucket: BUCKET_NAME }
});

答案 12 :(得分:0)

我使它尽可能简单。您可以使用for loop迭代上传对象,这非常简单,简洁并且易于理解。
所需的软件包:fs,express-fileupload

server.js:-

router.post('/upload', function(req, res){
    if(req.files){
        var file = req.files.filename;
        test(file);
    res.render('test');
}
} );

测试功能():-

function test(file){
  // upload all
  if(file.length){
    for(var i =0; i < file.length; i++){
      fileUP(file[i]); 
    }
  }else{
    fileUP(file);
}

  // call fileUP() to upload 1 at once
  function fileUP(fyl){
    var filename = fyl.name;
    var tempPath = './temp'+filename;
    fyl.mv(tempPath, function(err){
    fs.readFile(tempPath, function(err, data){
    var params = {
      Bucket: 'BUCKET_NAME',
      Body: data,
      Key: Date.now()+filename
    };

    s3.upload(params, function (err, data) {
      if (data) {
        fs.unlink(tempPath, (err) => {
          if (err) {
            console.error(err)
            return
          }
          else{
            console.log("file removed from temp loaction");
          }
        });
        console.log("Uploaded in:", data.Location);
      }
    });
  });
  });
  }

}

答案 13 :(得分:-1)

使用新API s3.listObjectsV2递归解决方案将是:

S3Dataset.prototype.listFiles = function(params,callback) {
    var self=this;

    var options = {
    };
    for (var attrname in params) { options[attrname] = params[attrname]; }

    var results=[];
    var s3=self.s3Store.GetInstance();
    function listAllKeys(token, callback) {
        var opt={ Bucket: self._options.s3.Bucket, Prefix: self._options.s3.Key, MaxKeys: 1000 };
        if(token) opt.ContinuationToken = token;
        s3.listObjectsV2(opt, (error, data) => {
            if (error) {
                if(self.logger) this.logger.error("listFiles error:", error);
                return callback(error);
            } else {
                for (var index in data.Contents) {
                    var bucket = data.Contents[index];
                    if(self.logger) self.logger.debug("listFiles Key: %s LastModified: %s Size: %s", bucket.Key, bucket.LastModified, bucket.Size);
                    if(bucket.Size>0) {
                        var Bucket=self._options.s3.Bucket;
                        var Key=bucket.Key;
                        var components=bucket.Key.split('/');
                        var name=components[components.length-1];
                        results.push({
                            name: name,
                            path: bucket.Key,
                            mtime: bucket.LastModified,
                            size: bucket.Size,
                            sizehr: formatSizeUnits(bucket.Size)
                        });
                    }
                }
                if( data.IsTruncated ) { // truncated page
                    return listAllKeys(data.NextContinuationToken, callback);
                } else {
                    return callback(null,results);
                }
            }
        });
    }
    return listAllKeys.apply(this,['',callback]);
};

,其中

function formatSizeUnits(bytes){
    if      (bytes>=1099511627776) {bytes=(bytes/1099511627776).toFixed(4)+' PB';}
    else if (bytes>=1073741824)    {bytes=(bytes/1073741824).toFixed(4)+' GB';}
    else if (bytes>=1048576)       {bytes=(bytes/1048576).toFixed(4)+' MB';}
    else if (bytes>=1024)          {bytes=(bytes/1024).toFixed(4)+' KB';}
    else if (bytes>1)              {bytes=bytes+' bytes';}
    else if (bytes==1)             {bytes=bytes+' byte';}
    else                           {bytes='0 byte';}
    return bytes;
}//formatSizeUnits

答案 14 :(得分:-3)

对我来说最干净的方法是通过我的节点脚本执行s3cmd(这里的示例是递归删除文件):

var exec = require('child_process').exec;
var child;
var bucket = "myBucket";
var prefix = "myPrefix"; // this parameter is optional
var command = "s3cmd del -r s3://" + bucket + "/" + prefix;
child = exec(command, {maxBuffer: 5000 * 1024}, function (error, stdout, stderr) { // the maxBuffer is here to avoid the maxBuffer node process error
            console.log('stdout: ' + stdout);
            if (error !== null) {
                console.log('exec error: ' + error);
            }
        });