【问题标题】:How to generate directory size recursively in python, like du . does?如何在 python 中递归生成目录大小,如 du 。做?
【发布时间】:2012-09-10 21:58:00
【问题描述】:

假设我的结构是这样的

/-- am here
/one/some/dir
/two
/three/has/many/leaves
/hello/world

假设 /one/some/dir 包含一个大文件,500mb,/three/has/many/leaves 在每个文件夹中包含一个 400mb 的文件。

我想为每个目录生成大小,以获得这个输出

/ - in total for all
/one/some/dir 500mb
/two 0 
/three/has/many/leaved - 400mb
/three/has/many 800
/three/has/ 800+someotherbigfilehere

我该怎么办?

【问题讨论】:

  • 我正在尝试理解您的问题。您要查找的输出与du -h . 的输出有何不同?
  • 我想要 du 的输出。是的。在蟒蛇。没有 usinb 子进程或执行 du。
  • 我刚刚为您链接了一种遍历文件的方法和一种获取文件大小的方法。你所要做的就是把它们加起来!您可能必须自己编写一些代码。对不起。
  • 您的 cmets 和回答表明,如果不是拒绝自己进行任何编码,则表示不情愿。如果您在考虑所有链接和/或提供的示例代码的情况下难以获得特定行为,请向我们展示您实际使用的内容,我们可以帮助指出正确的方向...如果你的态度是反复暗示“这个问题是世界上独一无二的,所以写给我”,你不太可能得到那个。
  • 如果你做了这样的修改,为什么不展示它们并解释你卡在哪里?不管怎样,盲目地、随机地修改代码,直到它完成你想要的,这不是编程的方式。

标签: python filesystems operating-system


【解决方案1】:

我使用pathlib 模块实现了这一点。以下代码将为给定目录树中的每个子目录计算正确的目录大小。


注意:如果您希望计算给定根目录的总大小,而不是使用此代码计算所有单独的子目录,那么您必须得到摆脱外部循环,即 - for sub in subdir: 并将 ls = list(sub.rglob('*.*')) 替换为 ls = list(dir_path.rglob('*.*')) 并相应地更正缩进。


所以,这里是在Windows 上使用Python 3.7.6 生成的示例代码。

import os 
from pathlib import Path

# Set home/root path
dir_path = Path('//?/C:/Downloads/.../.../.../.../...')

# IMP_NOTE: If the path is 265 characters long, which exceeds the classic MAX_PATH - 1 (259) character
# limit for DOS paths. Use an extended (verbatim) path such as "\\\\?\\C:\\" in order 
# to access the full length that's supported by the filesystem -- about 32,760 characters. 
# Alternatively, use Windows 10 with Python 3.6+ and enable long DOS paths in the registry.

# pathlib normalizes Windows paths to use backslash, so we can use
# Path('//?/D:/') without having to worry about escaping backslashes.

# Generate a complete list of sub-directories
subdir = list(x for x in dir_path.rglob('*') if x.is_dir())

for sub in subdir:
    tot_dir_size = 0
    ls = list(sub.rglob('*.*'))
    # print(sub, '\n')
    # print(len(ls), '\n')
    for k in ls:
        tot_dir_size += os.path.getsize(k)
    # print(format(tot_dir_size, ',d'))
    print("For Sub-directory: " + sub.parts[-1] + "   ===>   " + 
          "Size = " + str(format(tot_dir_size, ',d')) + "\n")

# path.parts ==> Provides a tuple giving access to the path’s various components
# (Ref.: pathlib documentation)


输出:



For Sub-directory: DIR_1   ===>   Size = 5,600,621,618

For Sub-directory: DIR_2   ===>   Size = 9,113,492,347

For Sub-directory: DIR_3   ===>   Size = 928,986,489

For Sub-directory: DIR_4   ===>   Size = 2,125,250,470

【讨论】:

    【解决方案2】:

    以下脚本打印指定目录的所有子目录的目录大小。该脚本应该独立于平台 - Posix/Windows/等。它还尝试从缓存递归函数的调用中受益(如果可能)。如果省略参数,则脚本将在当前目录中运行。输出按目录大小从大到小排序。因此,您可以根据需要对其进行调整。

    PS 我使用配方578019 以人性化的格式显示目录大小

    from __future__ import print_function
    import os
    import sys
    import operator
    
    def null_decorator(ob):
        return ob
    
    if sys.version_info >= (3,2,0):
        import functools
        my_cache_decorator = functools.lru_cache(maxsize=4096)
    else:
        my_cache_decorator = null_decorator
    
    start_dir = os.path.normpath(os.path.abspath(sys.argv[1])) if len(sys.argv) > 1 else '.'
    
    @my_cache_decorator
    def get_dir_size(start_path = '.'):
        total_size = 0
        if 'scandir' in dir(os):
            # using fast 'os.scandir' method (new in version 3.5)
            for entry in os.scandir(start_path):
                if entry.is_dir(follow_symlinks = False):
                    total_size += get_dir_size(entry.path)
                elif entry.is_file(follow_symlinks = False):
                    total_size += entry.stat().st_size
        else:
            # using slow, but compatible 'os.listdir' method
            for entry in os.listdir(start_path):
                full_path = os.path.abspath(os.path.join(start_path, entry))
                if os.path.islink(full_path):
                    continue
                if os.path.isdir(full_path):
                    total_size += get_dir_size(full_path)
                elif os.path.isfile(full_path):
                    total_size += os.path.getsize(full_path)
        return total_size
    
    def get_dir_size_walk(start_path = '.'):
        total_size = 0
        for dirpath, dirnames, filenames in os.walk(start_path):
            for f in filenames:
                fp = os.path.join(dirpath, f)
                total_size += os.path.getsize(fp)
        return total_size
    
    def bytes2human(n, format='%(value).0f%(symbol)s', symbols='customary'):
        """
        (c) http://code.activestate.com/recipes/578019/
    
        Convert n bytes into a human readable string based on format.
        symbols can be either "customary", "customary_ext", "iec" or "iec_ext",
        see: https://en.wikipedia.org/wiki/Binary_prefix#Specific_units_of_IEC_60027-2_A.2_and_ISO.2FIEC_80000
    
          >>> bytes2human(0)
          '0.0 B'
          >>> bytes2human(0.9)
          '0.0 B'
          >>> bytes2human(1)
          '1.0 B'
          >>> bytes2human(1.9)
          '1.0 B'
          >>> bytes2human(1024)
          '1.0 K'
          >>> bytes2human(1048576)
          '1.0 M'
          >>> bytes2human(1099511627776127398123789121)
          '909.5 Y'
    
          >>> bytes2human(9856, symbols="customary")
          '9.6 K'
          >>> bytes2human(9856, symbols="customary_ext")
          '9.6 kilo'
          >>> bytes2human(9856, symbols="iec")
          '9.6 Ki'
          >>> bytes2human(9856, symbols="iec_ext")
          '9.6 kibi'
    
          >>> bytes2human(10000, "%(value).1f %(symbol)s/sec")
          '9.8 K/sec'
    
          >>> # precision can be adjusted by playing with %f operator
          >>> bytes2human(10000, format="%(value).5f %(symbol)s")
          '9.76562 K'
        """
        SYMBOLS = {
            'customary'     : ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'),
            'customary_ext' : ('byte', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa',
                               'zetta', 'iotta'),
            'iec'           : ('Bi', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'),
            'iec_ext'       : ('byte', 'kibi', 'mebi', 'gibi', 'tebi', 'pebi', 'exbi',
                               'zebi', 'yobi'),
        }
        n = int(n)
        if n < 0:
            raise ValueError("n < 0")
        symbols = SYMBOLS[symbols]
        prefix = {}
        for i, s in enumerate(symbols[1:]):
            prefix[s] = 1 << (i+1)*10
        for symbol in reversed(symbols[1:]):
            if n >= prefix[symbol]:
                value = float(n) / prefix[symbol]
                return format % locals()
        return format % dict(symbol=symbols[0], value=n)
    
    ############################################################
    ###
    ###  main ()
    ###
    ############################################################
    if __name__ == '__main__':
        dir_tree = {}
        ### version, that uses 'slow' [os.walk method]
        #get_size = get_dir_size_walk
        ### this recursive version can benefit from caching the function calls (functools.lru_cache)
        get_size = get_dir_size
    
        for root, dirs, files in os.walk(start_dir):
            for d in dirs:
                dir_path = os.path.join(root, d)
                if os.path.isdir(dir_path):
                    dir_tree[dir_path] = get_size(dir_path)
    
        for d, size in sorted(dir_tree.items(), key=operator.itemgetter(1), reverse=True):
            print('%s\t%s' %(bytes2human(size, format='%(value).2f%(symbol)s'), d))
    
        print('-' * 80)
        if sys.version_info >= (3,2,0):
            print(get_dir_size.cache_info())
    

    样本输出:

    37.61M  .\subdir_b
    2.18M   .\subdir_a
    2.17M   .\subdir_a\subdir_a_2
    4.41K   .\subdir_a\subdir_a_1
    ----------------------------------------------------------
    CacheInfo(hits=2, misses=4, maxsize=4096, currsize=4)
    

    【讨论】:

    • 我不确定这个函数的作用,但它不是正确的:get_dir_size('/var/lib/docker/overlay') 产生 6889267157du -s /var/lib/docker/overlay 产生 612820 并且需要一个数量级更短。
    • @Qix,感谢您的评论!我刚刚发现我没有检查旧 Python 版本(
    • 同样的事情:/顺便说一句,我在 3.5.2 上。
    【解决方案3】:

    我用这段代码实现了这一点:

    def get_dir_size(path=os.getcwd()):
    
        total_size = 0
        for dirpath, dirnames, filenames in os.walk(path):
    
            dirsize = 0
            for f in filenames:
                fp = os.path.join(dirpath, f)
                size = os.path.getsize(fp)
                #print('\t',size, f)
                #print(dirpath, dirnames, filenames,size)
                dirsize += size
                total_size += size
            print('\t',dirsize, dirpath)
        print(" {0:.2f} Kb".format(total_size/1024))
    

    【讨论】:

      【解决方案4】:

      如果目录中有符号链接,实际上@mgilson 答案不起作用。为了允许你必须这样做:

      dirs_dict = {}
      for root, dirs, files in os.walk(directory, topdown=False):
          if os.path.islink(root):
              dirs_dict[root] = 0L
          else:
              dir_size = getsize(root)
      
              # Loop through every non directory file in this directory and sum their sizes
              for name in files:
                   full_name = join(root, name)
                   if os.path.islink(full_name):
                       nsize = 0L
                   else:
                       nsize = getsize(full_name)
                   dirs_dict[full_name] = nsize
                   dir_size += nsize
      
              # Look at all of the subdirectories and add up their sizes from the `dirs_dict`
              subdir_size = 0L
              for d in dirs:
                  full_d = join(root, d)
                  if os.path.islink(full_d):
                      dirs_dict[full_d] = 0L
                  else:
                      subdir_size += dirs_dict[full_d]
      
              dirs_dict[root] = dir_size + subdir_size
      

      【讨论】:

        【解决方案5】:

        看看os.walk。具体来说,文档中有一个查找目录大小的示例:

        import os
        from os.path import join, getsize
        for root, dirs, files in os.walk('python/Lib/email'):
            print root, "consumes",
            print sum(getsize(join(root, name)) for name in files),
            print "bytes in", len(files), "non-directory files"
            if 'CVS' in dirs:
                dirs.remove('CVS')  # don't visit CVS directories
        

        这应该很容易根据您的目的进行修改。


        以下是针对您的评论的未经测试的版本:

        import os
        from os.path import join, getsize
        dirs_dict = {}
        
        #We need to walk the tree from the bottom up so that a directory can have easy
        # access to the size of its subdirectories.
        for root, dirs, files in os.walk('python/Lib/email',topdown = False):
        
            # Loop through every non directory file in this directory and sum their sizes
            size = sum(getsize(join(root, name)) for name in files) 
        
            # Look at all of the subdirectories and add up their sizes from the `dirs_dict`
            subdir_size = sum(dirs_dict[join(root,d)] for d in dirs)
        
            # store the size of this directory (plus subdirectories) in a dict so we 
            # can access it later
            my_size = dirs_dict[root] = size + subdir_size
        
            print '%s: %d'%(root,my_size) 
        

        【讨论】:

        • 这对我来说似乎并不容易。我不会问我是否没有阅读文档并搜索了我能找到的所有内容。
        • @Antonioo -- 如果您删除 if 'CVS' in dirs 位,这难道不是您想要的吗?
        • 不,它没有,它给出的输出与 du 不同。,错误的大小。它给出了 /one 中的总文件大小,但我想要总文件大小 + /one 的所有子文件夹大小。
        • @Dave,这有多难,不是每个人都是天才,所以,是的,这很难。 os.walk 的文档也很难阅读,当每个人都告诉我这很简单时,尤其令人沮丧,而且我在互联网上到处看到人们只是吐出相同的代码,而没有真正解释它的作用和工作原理。这个答案中的上述内容也不起作用,也没有给出解释,但现在无论如何我都更近了一步,因为我看到我需要一个字典来保持每个子文件夹映射到它的大小。还有杜。给我一个空目录的不同结果,而 getsize 给 0。
        • @Antonioo -- 抱歉。我的编辑中有一个轻微的逻辑错误(它没有考虑 all 子目录——它只需要 1 个级别)。我已经更新了。此外,我对这个示例进行了大量评论,希望能解决“缺乏解释”的问题。
        猜你喜欢
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 2012-03-04
        • 2021-12-24
        • 1970-01-01
        • 1970-01-01
        • 2018-11-06
        • 2019-04-27
        相关资源
        最近更新 更多