在Amazon S3中列出给定级别的目录

时间:2009-08-06 15:30:52

标签: ruby amazon-s3 directory

我将200万个文件存储在亚马逊S3存储桶中。下面有一个给定的根(l1),l1下的目录列表,然后每个目录包含文件。所以我的桶看起来像下面的

l1/a1/file1-1.jpg
l1/a1/file1-2.jpg
l1/a1/... another 500 files
l1/a2/file2-1.jpg
l1/a2/file2-2.jpg
l1/a2/... another 500 files
....

l1/a5000/file5000-1.jpg

我想尽快列出第二级条目,所以我想得到a1,a2,a5000。我不想列出所有的密钥,这将花费更长的时间。

我愿意直接使用AWS api,但是到目前为止,我已经使用ruby中的right_aws gem进行了游戏http://rdoc.info/projects/rightscale/right_aws

该gem中至少有两个API,我尝试在S3模块中使用bucket.keys(),在S3Interface模块中使用incrementally_list_bucket()。例如,我可以设置前缀和分隔符列出所有l1 / a1 / *,但我无法弄清楚如何仅列出l1中的第一级。在incrementally_list_bucket()返回的哈希中有一个:common_prefixes条目,但是在我的测试样本中它没有被填充。

使用S3 API可以进行此操作吗?

谢谢!

2 个答案:

答案 0 :(得分:6)

right_aws允许将此作为​​其基础S3Interface类的一部分,但您可以创建自己的方法,以便更轻松(更好)地使用。把它放在代码的顶部:

module RightAws
  class S3
    class Bucket
      def common_prefixes(prefix, delimiter = '/')
        common_prefixes = []
        @s3.interface.incrementally_list_bucket(@name, { 'prefix' => prefix, 'delimiter' => delimiter }) do |thislist|          
          common_prefixes += thislist[:common_prefixes]
        end
        common_prefixes
      end
    end
  end
end

这会将common_prefixes方法添加到RightAws::S3::Bucket类。现在,您可以使用mybucket.keys来获取一系列公共前缀,而不是调用mybucket.common_prefixes来获取存储桶中的密钥列表。在你的情况下:

mybucket.common_prefixes("l1/")
# => ["l1/a1", "l1/a2", ... "l1/a5000"]

我必须说我只用少量公共前缀测试它;你应该检查这是否适用于超过1000个公共前缀。

答案 1 :(得分:0)

这个帖子已经很老了但是我最近遇到过这个问题,想要断言我的2cents ......

在S3存储桶中给出路径的干净地列出文件夹是一个麻烦的一半(似乎)。 S3 API(AWS-SDK官方,S3)周围的大多数当前gem包装器都没有正确解析返回对象(特别是CommonPrefixes),因此很难找回文件夹列表(分隔符噩梦)。

对于那些使用S3宝石的人来说,这是一个快速修复...对不起它不是一个尺寸,但它是我想做的最好。

https://github.com/qoobaa/s3/issues/61

代码段:

module S3
  class Bucket
    # this method recurses if the response coming back
    # from S3 includes a truncation flag (IsTruncated == 'true')
    # then parses the combined response(s) XML body
    # for CommonPrefixes/Prefix AKA directories
    def directory_list(options = {}, responses = [])
      options = {:delimiter => "/"}.merge(options)
      response = bucket_request(:get, :params => options)

      if is_truncated?(response.body)
        directory_list(options.merge({:marker => next_marker(response.body)}), responses << response.body)
      else
        parse_xml_array(responses + [response.body], options)
      end
    end

    private

    def parse_xml_array(xml_array, options = {}, clean_path = true)
      names = []
      xml_array.each do |xml|
        rexml_document(xml).elements.each("ListBucketResult/CommonPrefixes/Prefix") do |e|
          if clean_path
            names << e.text.gsub((options[:prefix] || ''), '').gsub((options[:delimiter] || ''), '')
          else
            names << e.text
          end
        end
      end
      names
    end

    def next_marker(xml)
      marker = nil
      rexml_document(xml).elements.each("ListBucketResult/NextMarker") {|e| marker ||= e.text }
      if marker.nil?
        raise StandardError
      else
        marker
      end
    end

    def is_truncated?(xml)
      is_truncated = nil
      rexml_document(xml).elements.each("ListBucketResult/IsTruncated") {|e| is_truncated ||= e.text }
      is_truncated == 'true'
    end
  end
end