Подтвердить что ты не робот

Не удается найти файлы с длинными именами в Windows с помощью Python

Мне нужно пройти через папки с длинными именами файлов в Windows.

Я попытался использовать os.listdir(), но он сбой с длинными именами путей, что плохо.

Я попытался использовать os.walk(), но он игнорирует пути, длина которых превышает ~ 256, что хуже.

Я пробовал обходное решение для магического слова, описанное здесь здесь, но оно работает только с подключенными дисками, а не с UNC pathnames.

Вот пример с короткими именами путей, которые показывают, что UNC-пути не работают с трюком магического слова.

>>> os.listdir('c:\\drivers')
['nusb3hub.cat', 'nusb3hub.inf', 'nusb3hub.sys', 'nusb3xhc.cat', 'nusb3xhc.inf', 'nusb3xhc.sys']
>>> os.listdir('\\\\Uni-hq-srv6\\router')
['2009-04-0210', '2010-11-0909', ... ]

>>> mw=u'\\\\?\\'
>>> os.listdir(mw+'c:\\drivers')
[u'nusb3hub.cat', u'nusb3hub.inf', u'nusb3hub.sys', u'nusb3xhc.cat', u'nusb3xhc.inf', u'nusb3xhc.sys']
>>> os.listdir(mw+'\\\\Uni-hq-srv6\\router')

Traceback (most recent call last):
  File "<pyshell#160>", line 1, in <module>
    os.listdir(mw+'\\\\Uni-hq-srv6\\router')
WindowsError: [Error 123] The filename, directory name, or volume label syntax is incorrect: u'\\\\?\\\\\\Uni-hq-srv6\\router\\*.*'

Любая идея о том, как обращаться с длинными именами путей или с UNC-именами unicode?

Edit:

Следуя предложению ниже, я создал некоторые тестовые функции для сравнения Python 2.7 и 3.3, и я добавил тест glob.glob и os.listdir после os.chdir.

os.chdir не помог, как ожидалось (см. этот комментарий).

glob.glob является единственным, что в Python 3.3 работает лучше, но только в одном условии: с использованием волшебного слова и с именем диска.

Вот код, который я использовал (он работает как с 2.7, так и с 3.3). Я изучаю Python сейчас, и я надеюсь, что эти тесты имеют смысл:

from __future__ import print_function
import os, glob

mw = u'\\\\?\\'

def walk(root):
    n = 0
    for root, dirs, files in os.walk(root):
        n += len(files)
    return n

def walk_mw(root):
    n = 0
    for root, dirs, files in os.walk(mw + root):
        n += len(files)
    return n

def listdir(root):
    try:
        folders = [f for f in os.listdir(root) if os.path.isdir(os.path.join(root, f))]
        files = [f for f in os.listdir(root) if os.path.isfile(os.path.join(root, f))]
        n = len(files)
        for f in folders:
            n += listdir(os.path.join(root, f))
        return n
    except:
        return 'Crash'

def listdir_mw(root):
    if not root.startswith(mw):
        root = mw + root
    try:
        folders = [f for f in os.listdir(root) if os.path.isdir(os.path.join(root, f))]
        files = [f for f in os.listdir(root) if os.path.isfile(os.path.join(root, f))]
        n = len(files)
        for f in folders:
            n += listdir_mw(os.path.join(root, f))
        return n
    except:
        return 'Crash'

def listdir_cd(root):
    try:
        os.chdir(root)
        folders = [f for f in os.listdir('.') if os.path.isdir(os.path.join(f))]
        files = [f for f in os.listdir('.') if os.path.isfile(os.path.join(f))]
        n = len(files)
        for f in folders:
            n += listdir_cd(f)
        return n
    except:
        return 'Crash'

def listdir_mw_cd(root):
    if not root.startswith(mw):
        root = mw + root
    try:
        os.chdir(root)
        folders = [f for f in os.listdir('.') if os.path.isdir(os.path.join(f))]
        files = [f for f in os.listdir('.') if os.path.isfile(os.path.join(f))]
        n = len(files)
        for f in folders:
            n += listdir_cd(f) # the magic word can only be added the first time
        return n
    except:
        return 'Crash'

def glb(root):
    folders = [f for f in glob.glob(root + '\\*') if os.path.isdir(os.path.join(root, f))]
    files = [f for f in glob.glob(root + '\\*') if os.path.isfile(os.path.join(root, f))]
    n = len(files)
    for f in folders:
        n += glb(os.path.join(root, f))
    return n

def glb_mw(root):
    if not root.startswith(mw):
        root = mw + root
    folders = [f for f in glob.glob(root + '\\*') if os.path.isdir(os.path.join(root, f))]
    files = [f for f in glob.glob(root + '\\*') if os.path.isfile(os.path.join(root, f))]
    n = len(files)
    for f in folders:
        n += glb_mw(os.path.join(root, f))
    return n

def test():
    for txt1, root in [('drive ', r'C:\test'),
                    ('UNC   ', r'\\Uni-hq-srv6\router\test')]:
        for txt2, func in [('walk                    ', walk),
                           ('walk     magic word     ', walk_mw),
                           ('listdir                 ', listdir),
                           ('listdir  magic word     ', listdir_mw),
                           ('listdir              cd ', listdir_cd),
                           ('listdir  magic word  cd ', listdir_mw_cd),
                           ('glob                    ', glb),
                           ('glob     magic word     ', glb_mw)]:
            print(txt1, txt2, func(root))

test()

И вот результат:

  • Число 8 означает, что все файлы найдены
  • Число 0 означает, что он даже не пытался без сбоев
  • Любое число от 1 до 7 означает, что он не прошел половину пути без сбоев
  • Слово Crash означает, что он разбился

-

Python 2.7
drive  walk                     5
drive  walk     magic word      8      * GOOD *
drive  listdir                  Crash
drive  listdir  magic word      8      * GOOD *
drive  listdir              cd  Crash
drive  listdir  magic word  cd  5
drive  glob                     5
drive  glob     magic word      0
UNC    walk                     6
UNC    walk     magic word      0
UNC    listdir                  5
UNC    listdir  magic word      Crash
UNC    listdir              cd  5
UNC    listdir  magic word  cd  Crash
UNC    glob                     5
UNC    glob     magic word      0

Python 3.3
drive  walk                     5
drive  walk     magic word      8      * GOOD *
drive  listdir                  Crash
drive  listdir  magic word      8      * GOOD *
drive  listdir              cd  Crash
drive  listdir  magic word  cd  5
drive  glob                     5
drive  glob     magic word      8      * GOOD *
UNC    walk                     6
UNC    walk     magic word      0
UNC    listdir                  5
UNC    listdir  magic word      Crash
UNC    listdir              cd  5
UNC    listdir  magic word  cd  Crash
UNC    glob                     5
UNC    glob     magic word      0
4b9b3361

Ответ 1

Используйте резерв 8.3, чтобы избежать длинного пути, просматривая в проводнике Win7, похоже, это то, что делают сами окна, т.е. каждый длинный путь имеет более короткое "истинное имя":

>>> long_unc="\\\\K53\\Users\\Tolan\\testing\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\xxxxxxxxxxxxxxxxxxxxxxxxdddddddddddddddddddddwgggggggggggggggggggggggggggggggggggxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\esssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggeee"
>>> os.listdir(long_unc)
FileNotFoundError: [WinError 3]

но вы можете использовать win32api (pywin32) для "построения" более короткой версии, то есть

short_unc=win32api.GetShortPathName(win32api.GetShortPathName(win32api.GetShortPathName("\\\\K53\\Users\\Tolan\\testing\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")+"\\xxxxxxxxxxxxxxxxxxxxxxxxdddddddddddddddddddddwgggggggggggggggggggggggggggggggggggxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + "\\esssssssssssssssssssssggggggggggggggggggggggggggggggggggggggggggggggeee")
>>> print(short_unc)
\\K53\Users\Tolan\testing\XXXXXX~1\XXXXXX~1\ESSSSS~1
>>> import os
>>> os.listdir(short_unc)
['test.txt']

ясно, что вы можете просто сбросить вызов win32api.GetShortPathName в исследование dir, а не вложенное, как в моем примере. Я сделал это, как это, с 3 вызовами, потому что, если у вас уже есть "слишком длинный" путь, то win32api.GetShortPathName тоже не справится с этим, но вы можете сделать это на диске и оставаться ниже предела.

Ответ 2

Чтобы найти файлы по UNC-путям, магический префикс \\?\UNC\, а не только \\?\.

Ссылка: https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath

Чтобы получить доступ к //server/share/really/deep/path/etc/etc, вам нужно

  • Преобразуйте его в unicode (используйте конструктор unicode())
  • Добавить магический префикс ("\\?\\UNC\") и
  • Убедитесь, что все разделители каталогов "\" (см. os.path.normpath())

Результирующая строка юникода: \\?\UNC\server\share\really\deep\path\etc\etc

Я только немного экспериментировал (намного меньше, чем @stenci), но с Python 2.7 он работает нормально с os.walk() и с ошибкой os.listdir().

Предостережение: он работает только с os.walk(), если начальный путь для обхода находится в пределах предела MAX_PATH, и ни одна из подкаталогов в стартовом пути не должна была бы перетащить его за пределы. Это связано с тем, что поскольку os.walk() использует os.listdir() в верхнем каталоге.

Ответ 3

В моем предыдущем комментарии я сказал, что вложенный рекурсивный вызов GetShortPathName не требуется. Я обнаружил, что это не требуется в большинстве случаев, но время от времени он падает. Я не мог понять, когда, так что я сделал эту небольшую функцию, которая работает гладко в течение некоторого времени:

Это функция, которую я использую сейчас:

def short_name(name):
    try:
        return win32api.GetShortPathName(name)
    except win32api.error:
        dirname = os.path.dirname(name)
        basename = os.path.basename(name)
        short_dirname = win32api.GetShortPathName(dirname)
        return win32api.GetShortPathName(os.path.join(short_dirname, basename))

try:
    mtime = os.path.getmtime(name)
except FileNotFoundError:
    name = short_name(name)
    mtime = os.path.getmtime(name)