如何找到文件所在的挂载点?

23
例如,我有一个文件,其路径如下:
/media/my_mountpoint/path/to/file.txt

我已经有整个路径,并想要获取:

/media/my_mountpoint

我应该如何做到这一点?最好使用Python并且不使用外部库/工具。(两者都不是必需的。)

9个回答

23

你可以调用 mount 命令并解析其输出以找到与你的路径最长公共前缀,或使用 stat 系统调用获取文件所驻留的设备,并向上遍历树直到达到不同的设备。

在 Python 中,stat 可以如下使用(未经测试,可能需要扩展以处理符号链接和像联合挂载这样的奇异东西):

def find_mount_point(path):
    path = os.path.abspath(path)
    orig_dev = os.stat(path).st_dev

    while path != '/':
        dir = os.path.dirname(path)
        if os.stat(dir).st_dev != orig_dev:
            # we crossed the device border
            break
        path = dir
    return path

编辑:我刚刚才知道os.path.ismount。这极大地简化了事情。

def find_mount_point(path):
    path = os.path.abspath(path)
    while not os.path.ismount(path):
        path = os.path.dirname(path)
    return path

3
目前我使用的几乎就是这个,只不过我在使用 os.path.ismount()。我认为可能有比沿着路径遍历更直接的方法。 - Georg Schölly
2
@Georg:我甚至不知道那个函数。我已经编辑了我的答案。 - Fred Foo
6
为了使你的代码与符号链接(例如/var/run -> ../run)配合使用,请将os.path.abspath()替换为os.path.realpath(),或者find_mount_point()将返回“/”。 - insecure
非常好的答案!我已经在 Golang 中实现了这个,非常有帮助。 - Jerry Jacobs

7

由于Python不是必需的:

df "$filename" | awk 'NR==1 {next} {print $6; exit}'
NR==1 {next} 的作用是跳过 df 命令输出的标题行。 $6 是挂载点。 exit 作用在于保证我们只输出一行。

2
对于我在 Gentoo 上,只有在使用 -P 标志进行兼容 POSIX 输出时才能正常工作。对于常规输出,如果路径过长,则文件系统列将单独显示在一行中,并且所有其他列值都会在下一行中显示。 - David Leuschner

6

由于现在在使用UUIDLABEL挂载文件系统的系统中,无法可靠地解析mount的内容,因为输出可能会包含以下内容:

(...)
/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000 on / type ext4 (rw,relatime,errors=remount-ro,data=ordered)
(...)

我们需要一个更健壮的解决方案(例如,考虑像上面那样“削减”路径的部分可能会导致什么,以及我们是否需要类似的东西)。
其中一种解决方案(顺便说一句,它尝试避免重复发明轮子)是简单地使用stat命令来发现文件所在的挂载点,如下所示:
$ stat --printf "%h:%m:%i\n" Talks
6:/media/lattes:461246

在上面的输出中,我们可以看到:
- `Talks` 中的硬链接数(`%h`)为 6 - 挂载点(`%m`)为 `/media/lattes` - 它的 inode 号码(`%i`)是 461246。
仅供参考,这是使用来自 GNU coreutils 的版本的 `stat`,这意味着其他版本(例如 BSD)可能不会默认拥有它(但您始终可以使用首选的软件包管理器安装它)。

这对于健壮性/可移植性/完整性/任何方面都非常出色。干得好。+1 - bballdave025

2
我正在使用Python开发一个基于GTK+3的文件管理器,当我循环处理文件时遇到了相同的需求。
我所使用的计算机有Linux和OS X分区。当文件管理器应用程序(在根Linux分区上运行)尝试索引OS X分区上的文件时,它会很快遇到从“/ media / mac-hd /用户指南和信息”到“/ Library / Documentation / User Guides and Information.localized”的绝对符号链接并阻塞。问题在于,文件管理器正在寻找该链接的绝对目标,在其自己的文件系统上,但在挂载在/ media / mac-hd的OS X分区上不存在。因此,我需要一种方法来确定文件是否在不同的挂载点上,并将该挂载点添加到链接的绝对目标之前。
我从Fred Foo的回答中开始编辑解决方案。它似乎有助于提供我试图解决的特定错误的解决方案。当我调用find_mount_point('/media/mac-hd/User Guides And Information')时,它将返回/media/mac-hd。我想这很好。
我注意到insecure在下面的评论中提到使其与符号链接一起工作,并且还注意到他关于/var/run的正确性:
为了使您的代码与符号链接(例如/var/run->../run)一起工作,请将os.path.abspath()替换为os.path.realpath(),否则find_mount_point()将返回“/”。
当我尝试用os.path.realpath()替换os.path.abspath()时,我将获得正确的返回值/run,对于/var/run。但是我还注意到,当调用find_mount_point('/media/mac-hd/User Guides And Information')时,我将不再获得所需的值,因为它现在返回/。
以下是我最终使用的解决方案。也许可以简化:
def find_mount_point(path):
    if not os.path.islink(path):
        path = os.path.abspath(path)
    elif os.path.islink(path) and os.path.lexists(os.readlink(path)):
        path = os.path.realpath(path)
    while not os.path.ismount(path):
        path = os.path.dirname(path)
        if os.path.islink(path) and os.path.lexists(os.readlink(path)):
            path = os.path.realpath(path)
    return path

2

@larsmans 的答案很好,非常有帮助!我在 Golang 中实现了这个功能。

对于那些对代码感兴趣的人(这已经在 OS X 和 Linux 上测试过):

package main

import (
    "os"
    "fmt"
    "syscall"
    "path/filepath"
)

func Mountpoint(path string) string {
    pi, err := os.Stat(path)
    if err != nil {
        return ""
    }

    odev := pi.Sys().(*syscall.Stat_t).Dev

    for path != "/" {
        _path := filepath.Dir(path)

        in, err := os.Stat(_path)
        if err != nil {
            return ""
        }

        if odev != in.Sys().(*syscall.Stat_t).Dev {
            break
        }

        path = _path
    }

    return path
}

func main() {
    path, _ := filepath.Abs("./")
    dir := filepath.Dir(path)
    fmt.Println("Path", path)
    fmt.Println("Dir", dir)
    fmt.Println("Mountpoint", Mountpoint(path))
}

1
我的Python已经有点生疏了,不过你可以用Perl类似这样的语法来实现:
export PATH_TO_LOOK_FOR="/media/path";
perl -ne '@p = split /\s+/; print "$p[1]\n" if "'$PATH_TO_LOOK_FOR'" =~ m@^$p[1]/@' < /proc/mounts

注意在$PATH_TO_LOOK_FOR周围加上" ' ' ",否则它将无法工作。

//编辑:Python解决方案:

def find_mountpoint(path):
    for l in open("/proc/mounts", "r"):
        mp = l.split(" ")[1]
        if(mp != "/" and path.find(mp)==0): return mp

    return None

0

os.path.realpath 移除符号链接,因此这样更简洁:

def find_mountpoint(path):
    """Find the non-symlinked mountpoint of a path."""

    path = os.path.realpath(path)

    while not os.path.ismount(path):
        path = os.path.dirname(path)

    return path

-3
/bin/mountpoint [-q] [-d] /path/to/directory

这不是Debian/Ubuntu特定的吗? - Fred Foo
2
@larsmans:这并不重要,因为它没有做到 OP 要求的。 - mtvec

-4
import os

def find_mount_point(path):
    while not os.path.ismount(path):
        path=os.path.dirname(path)
    return path

2
这与被接受的答案完全相同,唯一不同的是省略了必要的 abspath() 调用。 - Georg Schölly

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