在Python中查询块设备文件的大小

7
我有一个Python脚本,用于读取文件(通常来自光学媒体),标记无法读取的扇区,以便在不同的光学读卡器上重新尝试读取这些无法读取的扇区。
我发现我的脚本无法使用块设备(例如/dev/sr0)创建包含ISO9660/UDF文件系统的副本,因为os.stat().st_size为零。目前的算法需要提前知道文件大小;我可以改变它,但问题(如何知道块设备的大小)仍然存在,这里没有答案,所以我提出了这个问题。
我知道以下两个相关的SO问题:
确定块设备的大小(/proc/partitions,通过ctypes进行ioctl)
如何在Python中检查文件大小?(关于非特殊文件)
因此,我想问:在Python中,如何获取块设备文件的文件大小?
6个回答

9

我达到的“最干净”(即不依赖外部卷且最可重用)的Python解决方案是打开设备文件并在结尾处寻找,返回文件偏移量:

def get_file_size(filename):
    "Get the file size by seeking at end"
    fd= os.open(filename, os.O_RDONLY)
    try:
        return os.lseek(fd, 0, os.SEEK_END)
    finally:
        os.close(fd)

1
在两周内没有其他答案后,我选择了自己的回答。 - tzot
这需要使用sudo,但对于这样的操作来说是不必要的。 - Wolkenarchitekt

8

基于ioctl的Linux特定解决方案:

import fcntl
import struct

device_path = '/dev/sr0'

req = 0x80081272 # BLKGETSIZE64, result is bytes as unsigned 64-bit integer (uint64)
buf = b' ' * 8
fmt = 'L'

with open(device_path) as dev:
    buf = fcntl.ioctl(dev.fileno(), req, buf)
bytes = struct.unpack('L', buf)[0]

print device_path, 'is about', bytes / (1024 ** 2), 'megabytes'

当然,其他的 Unix 系统 req、buf、fmt 的值可能会有所不同。


这需要使用sudo,但对于这样的操作来说是不必要的。 - Wolkenarchitekt

4
另一个可能的解决方案是:
def blockdev_size(path):
    """Return device size in bytes.
    """
    with open(path, 'rb') as f:
        return f.seek(0, 2) or f.tell()

or f.tell() 部分是为了 Python 2 的可移植性而存在 - file.seek() 在Python 2 中返回 None。

神奇的常量 2 可以用 io.SEEK_END 替换。


1
谢谢你的回答。基本上最后一行可以在Python 2和3中都写成return f.seek(0, 2) or f.tell()。你说“更简洁的解决方案”相比于哪一个? - tzot
相比于“最干净”的代码(您的代码)。谢谢! - vvv
1
@tzot 我已经重新措辞了第一句,并采用了您的“或”建议。谢谢! - vvv
我的假设是f.seek()返回与f.tell()相同的值 - “返回一个整数,表示文件对象在二进制模式下从文件开头算起的字节数,以及在文本模式下的不透明数字”,但实际上这并没有在Python 3文档中提到。什么是“不透明”数字? - hansonap

3

在Linux中,即使没有sudo权限,也可以读取/sys/block/${dev}/size。要获取/dev/sdb的大小,请执行以下操作:

print( 512 * int(open('/sys/block/sdb/size','r').read()) )

另请参阅https://unix.stackexchange.com/a/52219/384116


0
这个解决方案使用lsblk(由util-linux提供)而且不需要sudo权限:
import json
import subprocess

def get_device_size_bytes(device: str) -> int:
    output = subprocess.run(
        ["lsblk", device, "--json", "--bytes"], capture_output=True, check=True
    )
    dev_info = json.loads(output.stdout)
    size_bytes = dev_info["blockdevices"][0]["size"]
    return size_bytes

从 lsblk 中仅获取大小的更短解决方案:

import subprocess

def get_device_size_bytes(device: str) -> int:
    output = subprocess.run(
        ["lsblk", device, "--output", "SIZE", "--bytes", "--noheadings", "--nodeps"],
        capture_output=True,
        check=True,
    )
    size = int(output.stdout.decode())
    return size

-1

尝试从其他答案中进行适应:

import fcntl
c = 0x00001260 ## check man ioctl_list, BLKGETSIZE
f = open('/dev/sr0', 'ro')
s = fcntl.ioctl(f, c)
print s

我手头没有合适的电脑来测试这个。如果它能够正常工作,我会很好奇的 :)


我尝试过使用f = openfd = os.open两种方式(因为fcntl.ioctl需要一个文件描述符,虽然它可能会在Python文件对象上调用.fileno())。但无论哪种情况,fcntl.ioctl都会引发IOError: [Errno 14] Bad address异常。 - tzot

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