web-dev-qa-db-ja.com

PythonのWindowsで、長い名前のファイルを見つけることができません

Windowsで長いファイル名のフォルダーをウォークスルーする必要があります。

os.listdir()を使用しようとしましたが、長いパス名でクラッシュします。これは悪いことです。

os.walk()を使用してみましたが、〜256より長いパス名は無視されます。これはさらに悪いことです。

ここ で説明されている魔法のWordの回避策を試しましたが、マップされたドライブでのみ機能し、 NCパス名 では機能しません。

これは短いパス名の例で、UNCパス名が魔法のWordトリックでは機能しないことを示しています。

>>> 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パス名を処理する方法についてのアイデアはありますか?

編集:

以下のコメントの提案に従って、Python 2.7と3.3を比較するためのいくつかのテスト関数を作成し、glob.globの後にos.listdiros.chdirのテストを追加しました。

os.chdirは期待どおりに機能しませんでした(これを参照 コメント )。

glob.globは、Python 3.3でより適切に機能する唯一のものですが、1つの条件でのみ、魔法の言葉を使用し、ドライブ名を使用します。

これが私が使用したコードです(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
30
stenci

8.3フォールバックを使用して、長いパス名を回避します。Win7Explorerで参照すると、これはWindows自体が行うことのようです。つまり、すべての長いパスには短い「実際の名前」があります。

>>> 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呼び出しをdirexplorationに折りたたむことができます。すでに「長すぎる」パスがある場合はwin32api.GetShortPathNameも対応できないため、3回の呼び出しでこのように実行しましたが、ディレクトリごとに実行して制限を下回ることができます。

6
tolanj

UNCパス上のファイルを見つけるための魔法のプレフィックスは、単なる_\\?\UNC\_ではなく_\\?\_です。

参照: https://msdn.Microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath

したがって、_//server/share/really/deep/path/etc/etc_にアクセスするには、次のことを行う必要があります。

  1. それをUnicodeに変換します(unicode()コンストラクターを使用します)
  2. 魔法の接頭辞(_"\\?\\UNC\"_)を追加し、
  3. すべてのディレクトリ区切り文字が_"\"_であることを確認します(os.path.normpath()を参照)

結果のUnicode文字列:_\\?\UNC\server\share\really\deep\path\etc\etc_

私は少しだけ実験しましたが(@stenciよりもはるかに少ないです)、Python 2.7では、os.walk()で問題なく動作し、os.listdir()

警告:トラバーサルの開始パスがMAX_PATH制限内にあり、開始パス内のどのサブディレクトリも制限を超えない場合にのみ、os.walk()で機能します。 。これは、os.walk()が最上位ディレクトリでos.listdir()を使用するためです。

5
randomsimon

以前のコメントで、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)
1
stenci