Python:读取带括号/长路径名的文件名时出现问题

Posted

技术标签:

【中文标题】Python:读取带括号/长路径名的文件名时出现问题【英文标题】:Python : Problem reading filename with brackets/long path name 【发布时间】:2019-11-23 19:51:20 【问题描述】:

我正在尝试使用 pandas 读取 excel 文件。

df=pd.read_excel('abcd (xyz-9) Interim Report 01-03-18.xlsx')

这给了我找不到文件的错误。如果我删除括号并将文件重命名为'abcd Interim Report 01-03-18.xlsx',那么它工作正常。

我尝试用shutil重命名,但它给了我同样的错误

shutil.copyfile('abcd (xyz-9) Interim Report 01-03-18.xlsx','test.xlsx')

我试过了

1. pd.read_excel('abcd ^(xyz-9) Interim Report 01-03-18.xlsx')
2. pd.read_excel('abcd \\(xyz-9\\) Interim Report 01-03-18.xlsx')

编辑:

即使我将 cwd 更改为文件位置,该文​​件似乎也可以在本地驱动器上运行,但不能在网络驱动器上运行。

关于使用 glob 和 os.path.exists:

for i in range(0,1):
    for filename in glob.glob(fpath+"\\"+ldir[i]+"\\"+"*Interim*.xlsx"):
        print(filename)
        print(os.path.exists(filename))
\\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx\06 xxx\02 xxx, xxx and xxxx xxx\03 xxx\04 xxx\05 xx xx & xx\12 2018 xx\06 xx xxx\\\AAA-61\abcd (xyz-9) Interim Report 01-03-18.xlsx
False

\\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx\06 xxx\02 xxx, xxx and xxxx xxx\03 xxx\04 xxx\05 xx xx & xx\12 2018 xx\06 xx xxx\\\AAA-61\abcd Interim Report 01-03-18.xlsx
True

关于使用 glob 和 os.stat:

import ctypes

for i in range(0,1):
    for filename in glob.glob(fpath+"\\"+ldir[i]+"\\"+"*Interim*.xlsx"):
        print(filename)
        try:
            print(os.stat(filename))
        except OSError as e: 
            ntstatus = ctypes.windll.ntdll.RtlGetLastNtStatus()
            print('winerror:', e.winerror) 
            print('ntstatus:', hex(ntstatus & (2**32-1)))
\\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx\06 xxx\02 xxx, xxx and xxxx xxx\03 xxx\04 xxx\05 xx xx & xx\12 2018 xx\06 xx xxx\\\AAA-61\abcd (xyz-9) Interim Report 01-03-18.xlsx
winerror: 3
ntstatus: 0x80000006

\\Africa-me.xxx.com\Africa-me\xxx\xxx\xxx\xxx\06 xxx\02 xxx, xxx and xxxx xxx\03 xxx\04 xxx\05 xx xx & xx\12 2018 xx\06 xx xxx\\\AAA-61\abcd Interim Report 01-03-18.xlsx
os.stat_result(st_mode=33206, st_ino=15624813576354602, st_dev=3657573641, st_nlink=1, st_uid=0, st_gid=0, st_size=726670, st_atime=1563172745, st_mtime=1523347973, st_ctime=1563170560) 

【问题讨论】:

Microsoft 的任何文件系统都不会保留括号(圆括号)。文件名中可能包含其他字符。通过[ascii(x) for x in os.listdir('path/to/containing/directory')] 列出目录。 ascii 函数将反斜杠转义所有非 ASCII 字符。 没有发现任何异常。是因为文件位于网络驱动器中吗?它似乎在本地驱动器上工作? 是的,复制原始文件并在同一网络位置的 Windows 资源管理器中重命名它。路径是 UNC。 请检查编辑。我使用 glob 通过 Interim 搜索列出文件,glob 能够找到这两个文件,但 os.path.exists 对括号之一显示为 false。 您认为问题出在读取权限上?因为我可以从 Windows 资源管理器手动打开文件,对于已经存在的其他文件,我可以在同一文件夹中使用 Python 读取它们 【参考方案1】:

os.stat 测试表明,访问带括号的路径会失败,并出现ERROR_PATH_NOT_FOUND (3),这可能来自缺少路径组件或路径太长。我们知道找到最终路径组件不是问题,因为在这种情况下,我们预计错误是ERROR_FILE_NOT_FOUND (2)。我们知道保留字符不是问题,因为在这种情况下,我们预计错误是ERROR_INVALID_NAME (123)。此外,在这两种情况下,Windows API 都必须进行 NT 系统调用,但我们看到最后一个 NT 状态是STATUS_NO_MORE_FILES (0x80000006),它来自glob.glob 中的os.listdir 调用。因此问题很可能是路径太长。

为了简洁或隐私,这个问题似乎将一些路径组件名称缩短为“xxx”。可能如果扩展到真实名称,我们会看到带有“(xyz-9)”的路径至少有 260 个字符,这比 DOS 路径允许的最大长度多一个,MAX_PATH - 1 (259)字符。

我们可以通过将长路径转换为扩展路径来访问它,这是一个以“\\?\”设备路径前缀或“\\?\UNC\”为 UNC 路径开头的 Unicode 字符串.首先,由于 Windows 中的字节路径仅限于 MAX_PATH 字符,因此我们必须将字节路径解码为 Unicode。接下来我们必须通过os.path.abspath 规范化和限定路径,因为扩展路径在访问时会绕过规范化。这是一个 extpath 函数,用于将 DOS 路径转换为扩展路径:

import os

try:
    from os import fsdecode
except ImportError: # Probably Python 2.x
    import sys
    def fsdecode(filename):
        if isinstance(filename, type(u'')):
            return filename
        elif isinstance(filename, bytes):
            return filename.decode(sys.getfilesystemencoding(), 'strict')
        raise TypeError('expected string, not '.format(
                type(filename).__name__))

def extpath(path):
    path = os.path.abspath(fsdecode(path))
    if not path.startswith(u'\\\\?\\'):
        if path.startswith(u'\\\\.\\'):
            path = u'\\\\?\\' + path[4:]
        elif path.startswith(u'\\\\'):
            path = u'\\\\?\\UNC\\' + path[2:]
        else:
            path = u'\\\\?\\' + path
    return path

背景

Windows NT 的核心(即自 XP 以来的所有 Windows 版本)是 NTOS 操作系统,它使用 NT 内核。这类似于 16 位 Windows 在 1980 年代和 1990 年代初期在 DOS 上分层的方式。但是 NTOS 与 Windows 的耦合比 DOS 更紧密,而且它是一个功能更强大的操作系统(例如,支持对称多处理、抢占式多线程、虚拟内存、异步 I/O、安全对象以及同时登录和会话的多个用户)。

在某些方面,Windows 仍然保持其 MS-DOS 根源。特别是对于磁盘设备和文件系统路径,Windows API 使用 DOS 路径而不是 NT 对象路径。这包括 DOS 驱动器“A:”到“Z:”和 UNC 路径,例如“\\server\share\path”。 Windows API 规范化 DOS 路径以用反斜杠替换正斜杠;使用进程工作目录或每个驱动器工作目录解析相对路径(即没有根目录或没有驱动器的路径);解决 ”。”和“..”组件;并从最终组件中修剪尾随空格和点。

访问 DOS 路径时,Windows 将其转换为以 WINAPI 设备前缀之一“\\.\”和“\\?\”(例如“C:\Windows”->“ \\?\C:\Windows")。对于驱动器号路径和相对路径(但不是 UNC 路径),它会在最终路径组件中保留一小组 DOS 设备名称(例如“C:\Temp\con”->“\\.\con”,以及“nul”->“\\.\nul”)。对于 UNC 路径,它使用“UNC”设备挂载点(例如 \\server\share\path ->“\\?\UNC\server\share\path”)。在调用传递到 NT 域之前,WINAPI 设备前缀(“\\.\”或“\\?\”)被 NTAPI 设备前缀(“\??\”)替换。

这些设备命名空间前缀是调用者在 NT 对象命名空间中的本地挂载点目录的简写(即“\Sessions\0\DosDevices\”)。本地挂载点目录隐式隐藏全局挂载点目录(即“\Global??”)。例如,“C:\Temp”变成“\??\C:\Temp”,如果全局“C:”没有被本地隐藏,则计算结果为“\Global??\C:\Temp”。全局挂载点可以通过“全局”对象链接(例如“\\?\Global\C:\Temp”)显式引用,该链接应该始终可用。

传统上,DOS 路径规范化使用字符串缓冲区,其空间不超过 MAX_PATH (260) 个字符。在 Windows 10 中,如果系统启用了长路径并且应用程序清单声明它可以识别长路径,则会取消此旧限制(在大多数情况下)。这 Python 3.6+ 中的“python[w].exe”可执行文件具有此清单设置。

如果未启用长 DOS 路径,大多数文件 API 函数仍支持长路径。我们只需要使用以“\\?\”前缀开头的 WINAPI 设备路径,这称为扩展路径。这类似于以“\\.\”前缀开头的常规设备路径,但扩展路径在访问时不会被规范化。缺点是我们必须实现自己的路径规范化。在 Python 中,这是由 os.path.abspath 实现的。我们仍然需要手动重写 UNC 路径,但只需将前导的“\\”替换为“\\?\UNC\”即可。

请注意,工作目录不支持设备路径,无论是否扩展。如果我们将设备路径设置为工作目录,则系统具有未定义的行为。所以不要将它们与 Python 的 os.chdirsubprocess.Popencwd 参数一起使用。这意味着我们无法通过将扩展路径设置为工作目录来绕过使用长相对路径的限制。在 Windows 10 中,如果为进程启用了长 DOS 路径,则工作目录确实支持长路径,但它仍然只支持常规 DOS 路径(UNC 和驱动器号路径),而不支持设备路径。

【讨论】:

【参考方案2】:

每当我处理文件时,我总是尝试使用 python 的pathlib 模块。文件名中的初始a 似乎有错误

import pandas as pd                                                        
import pathlib                                                             
path_to_file = pathlib.Path("g:\Python\abcd (xyz-9) Interim Report 01-03-18.xlsx")
df = pd.read_exces(path_to_file)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pandas' has no attribute 'read_exces'
df = pd.read_excel(path_to_file)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files\Python37\lib\site-packages\pandas\util\_decorators.py", line 188, in wrapper
    return func(*args, **kwargs)
  File "C:\Program Files\Python37\lib\site-packages\pandas\util\_decorators.py", line 188, in wrapper
    return func(*args, **kwargs)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 350, in read_excel
    io = ExcelFile(io, engine=engine)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 653, in __init__
    self._reader = self._engines[engine](self._io)
  File "C:\Program Files\Python37\lib\site-packages\pandas\io\excel.py", line 424, in __init__
    self.book = xlrd.open_workbook(filepath_or_buffer)
  File "C:\Program Files\Python37\lib\site-packages\xlrd\__init__.py", line 111, in open_workbook
    with open(filename, "rb") as f:
OSError: [Errno 22] Invalid argument: 'g:\\Python\x07bcd (xyz-9) Interim Report 01-03-18.xlsx'

似乎由于前面的反斜杠,最初的 a 在某些时候被解释为控制符号 \x07 U+0007 : ALERT [BEL]。

这就是为什么在定义路径时需要使用原始字符串,如Dawid suggested

path_to_file = pathlib.Path(r"g:\Python\abcd (xyz-9) Interim Report 01-03-18.xlsx")

【讨论】:

以上是关于Python:读取带括号/长路径名的文件名时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

python列名称有中文括号怎么读取

python3.10.2环境配置

为何python解决方法的文件最后有空格

python的imreadnewaxis

python 类带括号和不带括号的区别

Python学习系列之文件操作