【发布时间】:2011-08-24 01:40:45
【问题描述】:
我无法控制的子系统坚持以 uri 的形式提供文件系统路径。是否有 python 模块/函数可以将该路径转换为文件系统所需的适当形式,以独立于平台的方式?
【问题讨论】:
-
你要做的不仅仅是阅读它吗?
-
不,我想将该 uri 或等效形式传递到 python 模块中以进行路径操作
我无法控制的子系统坚持以 uri 的形式提供文件系统路径。是否有 python 模块/函数可以将该路径转换为文件系统所需的适当形式,以独立于平台的方式?
【问题讨论】:
使用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))
【讨论】:
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()) 中运行它,所以这个答案不准确。
@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'
【讨论】:
urllib.parse.unquote_plus,即“类似于unquote(),但也将加号替换为空格”。
unquote(urlparse('file:///C:/Program Files/Steam/').path) -> '/C:/Program Files/Steam/'
到目前为止,在所有答案中,我发现没有一个可以捕捉边缘情况、不需要分支、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 = "{0}{0}{mnt}{0}".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 = "{0}{0}{mnt}{0}".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: \t{path}"
"\nconverted to URI:\t{uri}"
"\nrebuilt normpath:\t{normpath}"
"\nrebuilt abspath:\t{absolutized}").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
【讨论】:
url2pathname 使用unquote 所以url2pathname(parsed.path) 应该足够了
foo%20bar.baz 将被您的解决方案正确编码为foo%2520bar.baz,但错误地解码为foo bar.baz。正如@dshanahan 所指出的,这是因为url2pathname 内部的unquote 不匹配
用 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 贡献的所有版权和相关或邻接权。本作品发表于:挪威。
【讨论】:
@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'
【讨论】: