有没有一种方便的方法可以将文件 uri 映射到 os.path?
Posted
技术标签:
【中文标题】有没有一种方便的方法可以将文件 uri 映射到 os.path?【英文标题】:Is there a convenient way to map a file uri to os.path? 【发布时间】:2011-08-24 01:40:45 【问题描述】:我无法控制的子系统坚持以 uri 的形式提供文件系统路径。是否有 python 模块/函数可以将该路径转换为文件系统所需的适当形式,以独立于平台的方式?
【问题讨论】:
你要做的不仅仅是阅读它吗? 不,我想将该 uri 或等效形式传递到 python 模块中以进行路径操作 【参考方案1】:使用urllib.parse.urlparse
从URI中获取路径:
import os
from urllib.parse import urlparse
p = urlparse('file://C:/test/doc.txt')
final_path = os.path.abspath(os.path.join(p.netloc, p.path))
【讨论】:
@JakobBowyer - 第二行中的 .path 应该被删除,否则你只是返回一个字符串到变量 p 而不是你需要在第三行处理的元组。C:\test\doc.txt
的有效文件 URI 是 file:///C:/test/doc.txt
而不是 file://C:/test/doc.txt
- 请参阅 IETF RFC 8089: The "file" URI Scheme / 2. Syntax 并在最近的 python 3 import pathlib; print(pathlib.PureWindowsPath("C:\\test\\doc.txt").as_uri())
中运行它,所以这个答案不准确。【参考方案2】:
@Jakob Bowyer 的解决方案不会将 URL encoded characters 转换为常规 UTF-8 字符。为此,您需要使用urllib.parse.unquote
。
>>> from urllib.parse import unquote, urlparse
>>> unquote(urlparse('file:///home/user/some%20file.txt').path)
'/home/user/some file.txt'
【讨论】:
@IwanAucamp 你能解释一下原因吗? 考虑使用urllib.parse.unquote_plus
,即“类似于unquote()
,但也将加号替换为空格”。
@Boris 因为为 windows 文件 URI 返回的路径以斜杠开头 unquote(urlparse('file:///C:/Program Files/Steam/').path)
-> '/C:/Program Files/Steam/'
非常适合我的 linux 环境【参考方案3】:
到目前为止,在所有答案中,我发现没有一个可以捕捉边缘情况、不需要分支、2/3 兼容、和跨平台。
简而言之,这可以完成工作,只使用内置函数:
try:
from urllib.parse import urlparse, unquote
from urllib.request import url2pathname
except ImportError:
# backwards compatability
from urlparse import urlparse
from urllib import unquote, url2pathname
def uri_to_path(uri):
parsed = urlparse(uri)
host = "00mnt0".format(os.path.sep, mnt=parsed.netloc)
return os.path.normpath(
os.path.join(host, url2pathname(unquote(parsed.path)))
)
棘手的一点(我发现)是在 Windows 中使用指定主机的路径工作时。这在 Windows 之外不是问题:*NIX 中的网络位置只能通过路径在被挂载到文件系统的根目录后访问。
来自Wikipedia:
文件 URI 采用 file://host/path
的形式,其中 host 是可访问路径的系统的完全限定域名 [...]。如果host省略,则取“localhost”。
考虑到这一点,我制定了一条规则,在将路径传递给 os.path.abspath
之前,始终使用 urlparse
提供的 netloc
作为路径前缀,这是必要,因为它删除任何由此产生的多余斜线(os.path.normpath
,它也声称可以修复斜线,在 Windows 中可能有点过分热心,因此使用abspath
)。
转换中的另一个关键组件是使用unquote
转义/解码 URL 百分比编码,否则您的文件系统将无法理解。同样,这在 Windows 上可能是一个更大的问题,它允许在路径中使用诸如 $
和 空格 之类的内容,这些内容将被编码在文件 URI 中。
对于演示:
import os
from pathlib import Path # This demo requires pip install for Python < 3.4
import sys
try:
from urllib.parse import urlparse, unquote
from urllib.request import url2pathname
except ImportError: # backwards compatability:
from urlparse import urlparse
from urllib import unquote, url2pathname
DIVIDER = "-" * 30
if sys.platform == "win32": # WINDOWS
filepaths = [
r"C:\Python27\Scripts\pip.exe",
r"C:\yikes\paths with spaces.txt",
r"\\localhost\c$\WINDOWS\clock.avi",
r"\\networkstorage\homes\rdekleer",
]
else: # *NIX
filepaths = [
os.path.expanduser("~/.profile"),
"/usr/share/python3/py3versions.py",
]
for path in filepaths:
uri = Path(path).as_uri()
parsed = urlparse(uri)
host = "00mnt0".format(os.path.sep, mnt=parsed.netloc)
normpath = os.path.normpath(
os.path.join(host, url2pathname(unquote(parsed.path)))
)
absolutized = os.path.abspath(
os.path.join(host, url2pathname(unquote(parsed.path)))
)
result = ("DIVIDER"
"\norig path: \tpath"
"\nconverted to URI:\turi"
"\nrebuilt normpath:\tnormpath"
"\nrebuilt abspath:\tabsolutized").format(**locals())
print(result)
assert path == absolutized
结果(WINDOWS):
------------------------------
orig path: C:\Python27\Scripts\pip.exe
converted to URI: file:///C:/Python27/Scripts/pip.exe
rebuilt normpath: C:\Python27\Scripts\pip.exe
rebuilt abspath: C:\Python27\Scripts\pip.exe
------------------------------
orig path: C:\yikes\paths with spaces.txt
converted to URI: file:///C:/yikes/paths%20with%20spaces.txt
rebuilt normpath: C:\yikes\paths with spaces.txt
rebuilt abspath: C:\yikes\paths with spaces.txt
------------------------------
orig path: \\localhost\c$\WINDOWS\clock.avi
converted to URI: file://localhost/c%24/WINDOWS/clock.avi
rebuilt normpath: \localhost\c$\WINDOWS\clock.avi
rebuilt abspath: \\localhost\c$\WINDOWS\clock.avi
------------------------------
orig path: \\networkstorage\homes\rdekleer
converted to URI: file://networkstorage/homes/rdekleer
rebuilt normpath: \networkstorage\homes\rdekleer
rebuilt abspath: \\networkstorage\homes\rdekleer
结果 (*NIX):
------------------------------
orig path: /home/rdekleer/.profile
converted to URI: file:///home/rdekleer/.profile
rebuilt normpath: /home/rdekleer/.profile
rebuilt abspath: /home/rdekleer/.profile
------------------------------
orig path: /usr/share/python3/py3versions.py
converted to URI: file:///usr/share/python3/py3versions.py
rebuilt normpath: /usr/share/python3/py3versions.py
rebuilt abspath: /usr/share/python3/py3versions.py
【讨论】:
根据documentationurl2pathname
使用unquote
所以url2pathname(parsed.path)
应该足够了
当编码的路径名包含类似 urlencode 的字符时,您的解决方案会中断。例如。文件名foo%20bar.baz
将被您的解决方案正确编码为foo%2520bar.baz
,但错误地解码为foo bar.baz
。正如@dshanahan 所指出的,这是因为url2pathname
内部的unquote
不匹配【参考方案4】:
用 python 将文件 uri 转换为路径(特定于 3,如果有人真的需要,我可以为 python 2 制作):
用urllib.parse.urlparse
解析uri
用urllib.parse.unquote
取消引用已解析uri的路径组件
然后...
一个。如果 path 是 windows 路径并且以 /
开头:去掉不带引号的路径组件的第一个字符(file:///C:/some/file.txt
的路径组件是 /C:/some/file.txt
,它不会被 pathlib.PureWindowsPath
解释为等同于 C:\some\file.txt
)
b.否则,请按原样使用未加引号的路径组件。
这是一个执行此操作的函数:
import urllib
import pathlib
def file_uri_to_path(file_uri, path_class=pathlib.PurePath):
"""
This function returns a pathlib.PurePath object for the supplied file URI.
:param str file_uri: The file URI ...
:param class path_class: The type of path in the file_uri. By default it uses
the system specific path pathlib.PurePath, to force a specific type of path
pass pathlib.PureWindowsPath or pathlib.PurePosixPath
:returns: the pathlib.PurePath object
:rtype: pathlib.PurePath
"""
windows_path = isinstance(path_class(),pathlib.PureWindowsPath)
file_uri_parsed = urllib.parse.urlparse(file_uri)
file_uri_path_unquoted = urllib.parse.unquote(file_uri_parsed.path)
if windows_path and file_uri_path_unquoted.startswith("/"):
result = path_class(file_uri_path_unquoted[1:])
else:
result = path_class(file_uri_path_unquoted)
if result.is_absolute() == False:
raise ValueError("Invalid file uri : resulting path not absolute".format(
file_uri, result))
return result
使用示例(在linux上运行):
>>> file_uri_to_path("file:///etc/hosts")
PurePosixPath('/etc/hosts')
>>> file_uri_to_path("file:///etc/hosts", pathlib.PurePosixPath)
PurePosixPath('/etc/hosts')
>>> file_uri_to_path("file:///C:/Program Files/Steam/", pathlib.PureWindowsPath)
PureWindowsPath('C:/Program Files/Steam')
>>> file_uri_to_path("file:/proc/cpuinfo", pathlib.PurePosixPath)
PurePosixPath('/proc/cpuinfo')
>>> file_uri_to_path("file:c:/system32/etc/hosts", pathlib.PureWindowsPath)
PureWindowsPath('c:/system32/etc/hosts')
此函数适用于 windows 和 posix 文件 URI,它将处理没有权限部分的文件 URI。但是,它不会验证 URI 的权限,因此不会兑现:
IETF RFC 8089: The "file" URI Scheme / 2. Syntax
“主机”是其所在系统的完全限定域名 该文件是可访问的。这允许另一个系统上的客户端 知道它无法访问文件系统,或者它可能需要 使用其他一些本地机制来访问文件。
函数的验证(pytest):
import os
import pytest
def validate(file_uri, expected_windows_path, expected_posix_path):
if expected_windows_path is not None:
expected_windows_path_object = pathlib.PureWindowsPath(expected_windows_path)
if expected_posix_path is not None:
expected_posix_path_object = pathlib.PurePosixPath(expected_posix_path)
if expected_windows_path is not None:
if os.name == "nt":
assert file_uri_to_path(file_uri) == expected_windows_path_object
assert file_uri_to_path(file_uri, pathlib.PureWindowsPath) == expected_windows_path_object
if expected_posix_path is not None:
if os.name != "nt":
assert file_uri_to_path(file_uri) == expected_posix_path_object
assert file_uri_to_path(file_uri, pathlib.PurePosixPath) == expected_posix_path_object
def test_some_paths():
validate(pathlib.PureWindowsPath(r"C:\Windows\System32\Drivers\etc\hosts").as_uri(),
expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")
validate(pathlib.PurePosixPath(r"/C:/Windows/System32/Drivers/etc/hosts").as_uri(),
expected_windows_path=r"C:\Windows\System32\Drivers\etc\hosts",
expected_posix_path=r"/C:/Windows/System32/Drivers/etc/hosts")
validate(pathlib.PureWindowsPath(r"C:\some dir\some file").as_uri(),
expected_windows_path=r"C:\some dir\some file",
expected_posix_path=r"/C:/some dir/some file")
validate(pathlib.PurePosixPath(r"/C:/some dir/some file").as_uri(),
expected_windows_path=r"C:\some dir\some file",
expected_posix_path=r"/C:/some dir/some file")
def test_invalid_url():
with pytest.raises(ValueError) as excinfo:
validate(r"file://C:/test/doc.txt",
expected_windows_path=r"test\doc.txt",
expected_posix_path=r"/test/doc.txt")
assert "is not absolute" in str(excinfo.value)
def test_escaped():
validate(r"file:///home/user/some%20file.txt",
expected_windows_path=None,
expected_posix_path=r"/home/user/some file.txt")
validate(r"file:///C:/some%20dir/some%20file.txt",
expected_windows_path="C:\some dir\some file.txt",
expected_posix_path=r"/C:/some dir/some file.txt")
def test_no_authority():
validate(r"file:c:/path/to/file",
expected_windows_path=r"c:\path\to\file",
expected_posix_path=None)
validate(r"file:/path/to/file",
expected_windows_path=None,
expected_posix_path=r"/path/to/file")
此贡献在Zero-Clause BSD License (0BSD) 许可下获得许可(除了可能适用的任何其他许可)
允许为任何人使用、复制、修改和/或分发此软件 特此授予有偿或无偿的目的。
本软件按“原样”提供,作者否认所有保证 关于本软件,包括所有默示保证 适销性和适应性。在任何情况下,作者概不负责 任何特殊、直接、间接或后果性损害或任何损害 因使用、数据或利润损失而导致的任何情况,无论是在 因合同、疏忽或其他侵权行为而引起的诉讼 或与本软件的使用或性能有关。
在法律允许的范围内,Iwan Aucamp 已放弃此 stackexchange 贡献的所有版权和相关或邻接权。本作品发表于:挪威。
【讨论】:
【参考方案5】:@colton7909 的解决方案大部分是正确的,并帮助我得到了这个答案,但在 Python 3 中存在一些导入错误。我认为这是处理 URL 的 'file://'
部分的更好方法,而不是简单地砍掉前 7 个字符。所以我觉得这是使用标准库最惯用的方法:
import urllib.parse
url_data = urllib.parse.urlparse('file:///home/user/some%20file.txt')
path = urllib.parse.unquote(url_data.path)
这个例子应该产生字符串'/home/user/some file.txt'
【讨论】:
以上是关于有没有一种方便的方法可以将文件 uri 映射到 os.path?的主要内容,如果未能解决你的问题,请参考以下文章
有没有一种直接的方法可以从文件 Uri 中获取父目录的 Uri?
NIOMappedByteBuffer-内存映射文件 I/O