查找包含给定文件的文件系统的大小和可用空间。

91

我在Linux下使用Python 2.6。 判断以下两个问题最快的方法:

  • 确定包含给定目录或文件的分区是哪个?

    例如,假设/dev/sda2挂载在/home上,/dev/mapper/foo挂载在/home/foo上。 我想从字符串"/home/foo/bar/baz"中恢复一对("/dev/mapper/foo", "home/foo")

  • 获取给定分区的使用统计信息?例如,针对/dev/mapper/foo,我想获得分区的大小和可用的剩余空间(以字节或大约以兆字节为单位)。


你是否考虑了符号链接?尽管你可能有 /home 和 /mnt/somedisk,但 /home/foo/x 可能是指向目录 /mnt/somedisk/xyzzy 的符号链接 - 因此它出现在 /home 下,但实际上位于 /mnt/somedisk。 - Piskvor left the building
@Piskvor:目前我不需要遵循符号链接,它们只是普通的目录。第一个问题基本上是在问“找到最近的祖先目录,该目录上挂载了一个分区”。 - Federico A. Ramponi
请参见https://dev59.com/vXA75IYBdhLWcg3weY_u。 - Mechanical snail
12个回答

145

这段代码没有给出分区的名称,但是您可以直接使用statvfs Unix系统调用来获取文件系统统计信息。要从Python中调用它,请使用os.statvfs('/home/foo/bar/baz')

根据POSIX标准,结果中的相关字段为according to POSIX:

unsigned long f_frsize   Fundamental file system block size. 
fsblkcnt_t    f_blocks   Total number of blocks on file system in units of f_frsize. 
fsblkcnt_t    f_bfree    Total number of free blocks. 
fsblkcnt_t    f_bavail   Number of free blocks available to 
                         non-privileged process.

因此,为了理解这些值,请乘以f_frsize

import os
statvfs = os.statvfs('/home/foo/bar/baz')

statvfs.f_frsize * statvfs.f_blocks     # Size of filesystem in bytes
statvfs.f_frsize * statvfs.f_bfree      # Actual number of free bytes
statvfs.f_frsize * statvfs.f_bavail     # Number of free bytes that ordinary users
                                        # are allowed to use (excl. reserved space)

我在使用带有ubifs的嵌入式系统时遇到了这个问题,这导致只有10MB可用空间的情况下出现了100MB可用空间的显示错误。我不确定这100MB是从哪里来的。 - Halfgaar
为什么得票最多的答案(领先很多)是 StackOverflow 列出的第六个结果? - bbrame

51

自Python 3.3起,标准库提供了一种简单直接的方法来实现此操作:

$ cat free_space.py 
#!/usr/bin/env python3

import shutil

total, used, free = shutil.disk_usage(__file__)
print(total, used, free)

$ ./free_space.py 
1007870246912 460794834944 495854989312

这些数字以字节为单位。更多信息请参见文档


51

如果你只需要设备上的可用空间,请参考下面使用os.statvfs()的答案。

如果你还需要文件所关联的设备名称和挂载点,你应该调用一个外部程序来获取这些信息。df 将提供你所需的所有信息--当作为df 文件名调用时,它将打印有关包含该文件的分区的一行信息。

举个例子:

import subprocess
df = subprocess.Popen(["df", "filename"], stdout=subprocess.PIPE)
output = df.communicate()[0]
device, size, used, available, percent, mountpoint = \
    output.split("\n")[1].split()
请注意,这种方法相当脆弱,因为它依赖于 df 输出的确切格式,但我不知道更加健壮的解决方案。(下面有几个依赖于/proc文件系统的解决方案,甚至比这个还不太可移植。)

1
具体来说,他可以执行导入命令,然后执行命令.getoutput("df filename | tail -1 | gawk '{ print $6 }' ")。 - dr jimbob
8
“commands” 模块已被“subprocess”取代。当我可以在 Python 中完成输出解析时,我不会在 bash 中执行它 :) - Sven Marnach
4
我不知道“df”的“filename”参数。使用“df -B MB filename”即可。非常感谢。 - Federico A. Ramponi
2
这种方法并不总是有效。在我的环境中,输出会占用多于一行的空间。在这种情况下,脚本会出现 ValueError('need more than 5 values to unpack' 的错误,因为设备列和其他信息位于不同的行中。 - liuyix
4
这个回答是针对Linux和GNU coreutils中的“df”命令的。如果您不需要设备名称和挂载点,请使用下一个回答中的代码。 - Sven Marnach
显示剩余13条评论

27
import os

def get_mount_point(pathname):
    "Get the mount point of the filesystem containing pathname"
    pathname= os.path.normcase(os.path.realpath(pathname))
    parent_device= path_device= os.stat(pathname).st_dev
    while parent_device == path_device:
        mount_point= pathname
        pathname= os.path.dirname(pathname)
        if pathname == mount_point: break
        parent_device= os.stat(pathname).st_dev
    return mount_point

def get_mounted_device(pathname):
    "Get the device mounted at pathname"
    # uses "/proc/mounts"
    pathname= os.path.normcase(pathname) # might be unnecessary here
    try:
        with open("/proc/mounts", "r") as ifp:
            for line in ifp:
                fields= line.rstrip('\n').split()
                # note that line above assumes that
                # no mount points contain whitespace
                if fields[1] == pathname:
                    return fields[0]
    except EnvironmentError:
        pass
    return None # explicit

def get_fs_freespace(pathname):
    "Get the free space of the filesystem containing pathname"
    stat= os.statvfs(pathname)
    # use f_bfree for superuser, or f_bavail if filesystem
    # has reserved space for superuser
    return stat.f_bfree*stat.f_bsize

我电脑上的一些示例路径名:

path 'trash':
  mp /home /dev/sda4
  free 6413754368
path 'smov':
  mp /mnt/S /dev/sde
  free 86761562112
path '/usr/local/lib':
  mp / rootfs
  free 2184364032
path '/proc/self/cmdline':
  mp /proc proc
  free 0

注意

如果您使用的是Python ≥ 3.3,可以使用shutil.disk_usage(path)函数来获取命名元组的磁盘使用情况,其中以字节表示的(total, used, free)信息。


如上所述:我在一个使用ubifs的嵌入式系统上尝试使用statvfs方法时失败了。它显示有100MB可用空间,但实际只有10MB。我不确定这100MB是从哪里来的。 - Halfgaar

16

这应该能满足你的所有需求:

import os
from collections import namedtuple

disk_ntuple = namedtuple('partition',  'device mountpoint fstype')
usage_ntuple = namedtuple('usage',  'total used free percent')

def disk_partitions(all=False):
    """Return all mountd partitions as a nameduple.
    If all == False return phyisical partitions only.
    """
    phydevs = []
    f = open("/proc/filesystems", "r")
    for line in f:
        if not line.startswith("nodev"):
            phydevs.append(line.strip())

    retlist = []
    f = open('/etc/mtab', "r")
    for line in f:
        if not all and line.startswith('none'):
            continue
        fields = line.split()
        device = fields[0]
        mountpoint = fields[1]
        fstype = fields[2]
        if not all and fstype not in phydevs:
            continue
        if device == 'none':
            device = ''
        ntuple = disk_ntuple(device, mountpoint, fstype)
        retlist.append(ntuple)
    return retlist

def disk_usage(path):
    """Return disk usage associated with path."""
    st = os.statvfs(path)
    free = (st.f_bavail * st.f_frsize)
    total = (st.f_blocks * st.f_frsize)
    used = (st.f_blocks - st.f_bfree) * st.f_frsize
    try:
        percent = ret = (float(used) / total) * 100
    except ZeroDivisionError:
        percent = 0
    # NB: the percentage is -5% than what shown by df due to
    # reserved blocks that we are currently not considering:
    # http://goo.gl/sWGbH
    return usage_ntuple(total, used, free, round(percent, 1))


if __name__ == '__main__':
    for part in disk_partitions():
        print part
        print "    %s\n" % str(disk_usage(part.mountpoint))

在我的电脑上,上述代码输出:

giampaolo@ubuntu:~/dev$ python foo.py 
partition(device='/dev/sda3', mountpoint='/', fstype='ext4')
    usage(total=21378641920, used=4886749184, free=15405903872, percent=22.9)

partition(device='/dev/sda7', mountpoint='/home', fstype='ext4')
    usage(total=30227386368, used=12137168896, free=16554737664, percent=40.2)

partition(device='/dev/sdb1', mountpoint='/media/1CA0-065B', fstype='vfat')
    usage(total=7952400384, used=32768, free=7952367616, percent=0.0)

partition(device='/dev/sr0', mountpoint='/media/WB2PFRE_IT', fstype='iso9660')
    usage(total=695730176, used=695730176, free=0, percent=100.0)

partition(device='/dev/sda6', mountpoint='/media/Dati', fstype='fuseblk')
    usage(total=914217758720, used=614345637888, free=299872120832, percent=67.2)

1
还有,请看这个代码实例:http://code.activestate.com/recipes/577972-disk-usage/ - Giampaolo Rodolà
一个小问题 - all 是一个内置函数,不应该在函数中用作变量。 - Adam Matan
这可以用千兆字节来表示吗? - mysqlrockstar

10

找出它最简单的方法。

import os
from collections import namedtuple

DiskUsage = namedtuple('DiskUsage', 'total used free')

def disk_usage(path):
    """Return disk usage statistics about the given path.

    Will return the namedtuple with attributes: 'total', 'used' and 'free',
    which are the amount of total, used and free space, in bytes.
    """
    st = os.statvfs(path)
    free = st.f_bavail * st.f_frsize
    total = st.f_blocks * st.f_frsize
    used = (st.f_blocks - st.f_bfree) * st.f_frsize
    return DiskUsage(total, used, free)

已使用 = 总量 - 空闲 - AK47
当我运行上面的示例时,我会收到一个有关缺少属性的错误:AttributeError: module 'os' has no attribute 'statvfs'。我做错了什么? - Rich Lysakowski PhD

9
对于你的第二个问题,“获取给定分区的使用统计信息”,psutil 使用 disk_usage(path) 函数可以轻松实现。给定路径,disk_usage() 函数返回一个命名元组,其中包括以字节表示的总空间、已使用空间和可用空间,以及使用百分比。

以下是文档中的简单示例:

>>> import psutil
>>> psutil.disk_usage('/')
sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)

Psutil适用于Python 2.6到3.6版本,以及Linux、Windows和OSX等其他平台。


6
对于第一个问题,您可以尝试使用os.path.realpath来获取规范路径,并将其与/etc/mtab进行比较(实际上我建议调用getmntent,但我找不到正常的访问方式)以查找最长匹配项。(为了确保,您应该可能要stat文件和假定的挂载点以验证它们实际上在同一设备上)
对于第二个问题,请使用os.statvfs来获取块大小和使用信息。
(免责声明:我没有测试过这些内容,我所知道的大部分来自coreutils源代码)

re getmntent:嗯,总是有 import ctypes; ctypes.cdll.LoadLibrary("libc.so.6").getmntent 的可能性,但这并不那么简单... - tzot
我很好奇为什么这个被踩了,留个评论会更好。 - Hasturkun

6
import os

def disk_stat(path):
    disk = os.statvfs(path)
    percent = (disk.f_blocks - disk.f_bfree) * 100 / (disk.f_blocks -disk.f_bfree + disk.f_bavail) + 1
    return percent


print disk_stat('/')
print disk_stat('/data')

2
虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文将改善答案的长期价值。 - Donald Duck
disk_stat方法不需要任何参数。但是,使用os.statvfs的想法很好。 - suripoori

3
11年后,但是在其他人的答案基础上进行了扩展。
import psutil

#File systems
value=psutil.disk_partitions()

for i in value:
    va=i[1]
    value2=psutil.disk_usage(va).percent
    print(value2)
    fs_space[va]=value2

这是将其添加到字典中,只获取百分比,因为这是我需要的,但您可以从总数、已使用、可用或百分比中获取所有值或选择所需的一个。

官方文档帮了很大的忙


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接