2009-09-19 4 views
30

누구나 파이썬에서 win32 버전의 심볼릭 링크를 만들고 읽는 방법을 알고 있습니까? 이상적으로는 플랫폼 별 코드가 최소한 있어야합니다. 앱을 크로스 플랫폼으로 사용해야합니다.Windows의 심볼릭 링크?

+3

내가 필요로하는 기능은 파일 시스템의 서로 다른 장소에서 파일에 대한 링크를 포함하는 디렉토리를 만든 다음 그들이 그 디렉토리에있는 것처럼 내 파이썬 코드가 해당 파일을 열 수있을 수있을 것입니다. –

+0

왜 그 기능이 필요합니까? –

답변

10

문제는 설명 된대로입니다. here, 심볼 링크의 기능에 대한 Windows의 자체 지원은 Windows 릴리스마다 다르므로 예를 들어. 비스타에서는 (많은 작업을 통해) XP 나 2000보다 더 많은 기능을 사용할 수 있습니다 (다른 win32 버전에서는 AFAIK가 없습니다). 또는 대신 단축키를 사용할 수 있습니다. 물론 단축키는 고유 한 제한 집합을 가지고 있으며 실제로는 유닉스 심볼 링크와 동등하지 않습니다. 따라서, 여러분이 요구하는 기능을 정확하게 지정해야하며, cross-win32 작업의 제단에서 희생하고자하는 것들 중 얼마만큼을 지정해야합니다. 그런 다음, 우리는 여러분이 선택한 절충안을 어떻게 구현할 것인지를 결정할 수 있습니다 ctypes 또는 win32all이 호출됩니다. 그 의미는 가장 적습니다.

34

NTFS 파일 시스템에 연결점이 있으므로 대신 사용할 수 있다고 생각합니다. 예를 들어 Python Win32 API 모듈을 사용할 수 있습니다. 예 : 당신이 win32API 모듈에 의존하지 않으려면

import win32file 

win32file.CreateSymbolicLink(fileSrc, fileTarget, 1) 

, 당신은 항상 ctypes를 사용하여 직접, 예를 들어 CreateSymbolicLink는 Win32 API를 호출 할 수 있습니다

import ctypes 

kdll = ctypes.windll.LoadLibrary("kernel32.dll") 

kdll.CreateSymbolicLinkA("d:\\test.txt", "d:\\test_link.txt", 0) 

MSDN (http://msdn.microsoft.com/en-us/library/aa363866(VS.85).aspx가) 최소 클라이언트가 윈도우 비스타 또한에서

지원 말한다 :이 또한 디렉토리와 함께 작동 (그 세 번째 인수로 표시). 유니 코드 지원으로는 다음과 같습니다

kdll.CreateSymbolicLinkW(UR"D:\testdirLink", UR"D:\testdir", 1) 

또한 내가 lib 디렉토리/사이트 패키지/sitecustomize.py에 다음을 넣어 Create NTFS junction point in Python

+11

당신은'..A' Windows API를 사용해서는 안되며 매번'..W' (유니 코드)를 사용하십시오. – sorin

+0

유니 코드를 사용하여 디렉토리 링크의 예제를 추가했습니다. –

+1

'win32file.CreateSymbolicLink'에 표시된 매개 변수 이름은 다소 혼란 스럽습니다. 궁금한 사람들을 위해, 첫 번째는 만들 링크의 이름이고, 두 번째는 링크해야 할 경로입니다. – brianmearns

6

를 참조

import os 

__CSL = None 
def symlink(source, link_name): 
    '''symlink(source, link_name) 
     Creates a symbolic link pointing to source named link_name''' 
    global __CSL 
    if __CSL is None: 
     import ctypes 
     csl = ctypes.windll.kernel32.CreateSymbolicLinkW 
     csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) 
     csl.restype = ctypes.c_ubyte 
     __CSL = csl 
    flags = 0 
    if source is not None and os.path.isdir(source): 
     flags = 1 
    if __CSL(link_name, source, flags) == 0: 
     raise ctypes.WinError() 

os.symlink = symlink 
12

python ntfslink extension

또는 pywin32를 사용하고 싶다면 이전에 언급 한 방법을 사용하고 읽을 수 있습니다 :

from win32file import * 
from winioctlcon import FSCTL_GET_REPARSE_POINT 

__all__ = ['islink', 'readlink'] 

# Win32file doesn't seem to have this attribute. 
FILE_ATTRIBUTE_REPARSE_POINT = 1024 
# To make things easier. 
REPARSE_FOLDER = (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) 

# For the parse_reparse_buffer function 
SYMBOLIC_LINK = 'symbolic' 
MOUNTPOINT = 'mountpoint' 
GENERIC = 'generic' 

def islink(fpath): 
    """ Windows islink implementation. """ 
    if GetFileAttributes(fpath) & REPARSE_FOLDER == REPARSE_FOLDER: 
     return True 
    return False 


def parse_reparse_buffer(original, reparse_type=SYMBOLIC_LINK): 
    """ Implementing the below in Python: 

    typedef struct _REPARSE_DATA_BUFFER { 
     ULONG ReparseTag; 
     USHORT ReparseDataLength; 
     USHORT Reserved; 
     union { 
      struct { 
       USHORT SubstituteNameOffset; 
       USHORT SubstituteNameLength; 
       USHORT PrintNameOffset; 
       USHORT PrintNameLength; 
       ULONG Flags; 
       WCHAR PathBuffer[1]; 
      } SymbolicLinkReparseBuffer; 
      struct { 
       USHORT SubstituteNameOffset; 
       USHORT SubstituteNameLength; 
       USHORT PrintNameOffset; 
       USHORT PrintNameLength; 
       WCHAR PathBuffer[1]; 
      } MountPointReparseBuffer; 
      struct { 
       UCHAR DataBuffer[1]; 
      } GenericReparseBuffer; 
     } DUMMYUNIONNAME; 
    } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; 

    """ 
    # Size of our data types 
    SZULONG = 4 # sizeof(ULONG) 
    SZUSHORT = 2 # sizeof(USHORT) 

    # Our structure. 
    # Probably a better way to iterate a dictionary in a particular order, 
    # but I was in a hurry, unfortunately, so I used pkeys. 
    buffer = { 
     'tag' : SZULONG, 
     'data_length' : SZUSHORT, 
     'reserved' : SZUSHORT, 
     SYMBOLIC_LINK : { 
      'substitute_name_offset' : SZUSHORT, 
      'substitute_name_length' : SZUSHORT, 
      'print_name_offset' : SZUSHORT, 
      'print_name_length' : SZUSHORT, 
      'flags' : SZULONG, 
      'buffer' : u'', 
      'pkeys' : [ 
       'substitute_name_offset', 
       'substitute_name_length', 
       'print_name_offset', 
       'print_name_length', 
       'flags', 
      ] 
     }, 
     MOUNTPOINT : { 
      'substitute_name_offset' : SZUSHORT, 
      'substitute_name_length' : SZUSHORT, 
      'print_name_offset' : SZUSHORT, 
      'print_name_length' : SZUSHORT, 
      'buffer' : u'', 
      'pkeys' : [ 
       'substitute_name_offset', 
       'substitute_name_length', 
       'print_name_offset', 
       'print_name_length', 
      ] 
     }, 
     GENERIC : { 
      'pkeys' : [], 
      'buffer': '' 
     } 
    } 

    # Header stuff 
    buffer['tag'] = original[:SZULONG] 
    buffer['data_length'] = original[SZULONG:SZUSHORT] 
    buffer['reserved'] = original[SZULONG+SZUSHORT:SZUSHORT] 
    original = original[8:] 

    # Parsing 
    k = reparse_type 
    for c in buffer[k]['pkeys']: 
     if type(buffer[k][c]) == int: 
      sz = buffer[k][c] 
      bytes = original[:sz] 
      buffer[k][c] = 0 
      for b in bytes: 
       n = ord(b) 
       if n: 
        buffer[k][c] += n 
      original = original[sz:] 

    # Using the offset and length's grabbed, we'll set the buffer. 
    buffer[k]['buffer'] = original 
    return buffer 

def readlink(fpath): 
    """ Windows readlink implementation. """ 
    # This wouldn't return true if the file didn't exist, as far as I know. 
    if not islink(fpath): 
     return None 

    # Open the file correctly depending on the string type. 
    handle = CreateFileW(fpath, GENERIC_READ, 0, None, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0) \ 
       if type(fpath) == unicode else \ 
      CreateFile(fpath, GENERIC_READ, 0, None, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0) 

    # MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16*1024) 
    buffer = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 16*1024) 
    # Above will return an ugly string (byte array), so we'll need to parse it. 

    # But first, we'll close the handle to our file so we're not locking it anymore. 
    CloseHandle(handle) 

    # Minimum possible length (assuming that the length of the target is bigger than 0) 
    if len(buffer) < 9: 
     return None 
    # Parse and return our result. 
    result = parse_reparse_buffer(buffer) 
    offset = result[SYMBOLIC_LINK]['substitute_name_offset'] 
    ending = offset + result[SYMBOLIC_LINK]['substitute_name_length'] 
    rpath = result[SYMBOLIC_LINK]['buffer'][offset:ending].replace('\x00','') 
    if len(rpath) > 4 and rpath[0:4] == '\\??\\': 
     rpath = rpath[4:] 
    return rpath 

def realpath(fpath): 
    from os import path 
    while islink(fpath): 
     rpath = readlink(fpath) 
     if not path.isabs(rpath): 
      rpath = path.abspath(path.join(path.dirname(fpath), rpath)) 
     fpath = rpath 
    return fpath 


def example(): 
    from os import system, unlink 
    system('cmd.exe /c echo Hello World > test.txt') 
    system('mklink test-link.txt test.txt') 
    print 'IsLink: %s' % islink('test-link.txt') 
    print 'ReadLink: %s' % readlink('test-link.txt') 
    print 'RealPath: %s' % realpath('test-link.txt') 
    unlink('test-link.txt') 
    unlink('test.txt') 

if __name__=='__main__': 
    example() 

CreateFile의 특성을 필요에 맞게 조정하지만 정상적인 경우에는 정상적으로 작동합니다. 기분 전환하십시오.

SYMBOLIC_LINK 대신 MOUNTPOINT를 사용하면 폴더 접합에도 사용할 수 있습니다.

당신은 방법은이 심볼릭 링크의 형태로 있기 때문에, 당신은 해제하고 뭔가에 유입되면

sys.getwindowsversion()[0] >= 6 

은 비스타 +에서 지원되는지 확인 할 수 있습니다.

+0

수정 사항을 제출했지만 실제로 버그가 있음에도 불구하고 거부되었습니다. islink()에서 가면 값은 가양 성을 피하기 위해 마스크와 비교되어야합니다 (0 = false/nonzero = true 단축키는 단일 비트 마스크 (예 : 1024)에서만 사용됨). 그렇지 않으면 정상적인 dirs는 "링크"로 식별됩니다. "GetFileAttributes (fpath) & REPARSE_FOLDER == REPARSE_FOLDER : – MartyMacGyver

+0

만약 내가 그것을 거절했다면, 나는 틀림없이 그렇게했을 것입니다. 나는 그것에 대해 미안합니다. (솔직히, 나는 편집 요구를 보러 갈 곳이 어디인지 모르겠다. 나는 여기에 하나도받지 못했다.) 나는 앞서 가서 수정하여 업데이트했지만, 그렇게 해 주셔서 감사합니다. –

+0

아뇨, * 당신이 아무 것도 거절하지 않았습니다 ... 다른 두 편집자가했는데, 제 제안 된 편집이 단순히 버그를 바로 잡았 기 때문에 특이했습니다. 즉, 귀하의 게시물에 다시 한 번 감사드립니다. :-) – MartyMacGyver

1

Juntalis의 코드는 유니 코드를 처리하지 않으므로 ctypes를 사용하도록 수정했으며 struct를 사용하여 단순화했습니다. 나는 또한 NTFS 파일 시스템에서 Windows 8.1을 사용하여 파이썬 3.3에서 작동 os.symlink Using a struct as a function argument with the python ctypes module

import os, ctypes, struct 
from ctypes import windll, wintypes 

FSCTL_GET_REPARSE_POINT = 0x900a8 

FILE_ATTRIBUTE_READONLY  = 0x0001 
FILE_ATTRIBUTE_HIDDEN  = 0x0002 
FILE_ATTRIBUTE_DIRECTORY  = 0x0010 
FILE_ATTRIBUTE_NORMAL  = 0x0080 
FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 


GENERIC_READ = 0x80000000 
GENERIC_WRITE = 0x40000000 
OPEN_EXISTING = 3 
FILE_READ_ATTRIBUTES = 0x80 
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value 

INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF 

FILE_FLAG_OPEN_REPARSE_POINT = 2097152 
FILE_FLAG_BACKUP_SEMANTICS = 33554432 
# FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTI 
FILE_FLAG_REPARSE_BACKUP = 35651584 


GetFileAttributes = windll.kernel32.GetFileAttributesW 
_CreateFileW = windll.kernel32.CreateFileW 
_DevIoCtl = windll.kernel32.DeviceIoControl 
_DevIoCtl.argtypes = [ 
    wintypes.HANDLE, #HANDLE hDevice 
    wintypes.DWORD, #DWORD dwIoControlCode 
    wintypes.LPVOID, #LPVOID lpInBuffer 
    wintypes.DWORD, #DWORD nInBufferSize 
    wintypes.LPVOID, #LPVOID lpOutBuffer 
    wintypes.DWORD, #DWORD nOutBufferSize 
    ctypes.POINTER(wintypes.DWORD), #LPDWORD lpBytesReturned 
    wintypes.LPVOID] #LPOVERLAPPED lpOverlapped 
_DevIoCtl.restype = wintypes.BOOL 


def islink(path): 
    assert os.path.isdir(path), path 
    if GetFileAttributes(path) & FILE_ATTRIBUTE_REPARSE_POINT: 
     return True 
    else: 
     return False 


def DeviceIoControl(hDevice, ioControlCode, input, output): 
    # DeviceIoControl Function 
    # http://msdn.microsoft.com/en-us/library/aa363216(v=vs.85).aspx 
    if input: 
     input_size = len(input) 
    else: 
     input_size = 0 
    if isinstance(output, int): 
     output = ctypes.create_string_buffer(output) 
    output_size = len(output) 
    assert isinstance(output, ctypes.Array) 
    bytesReturned = wintypes.DWORD() 
    status = _DevIoCtl(hDevice, ioControlCode, input, 
         input_size, output, output_size, bytesReturned, None) 
    print "status(%d)" % status 
    if status != 0: 
     return output[:bytesReturned.value] 
    else: 
     return None 


def CreateFile(path, access, sharemode, creation, flags): 
    return _CreateFileW(path, access, sharemode, None, creation, flags, None) 


SymbolicLinkReparseFormat = "LHHHHHHL" 
SymbolicLinkReparseSize = struct.calcsize(SymbolicLinkReparseFormat); 

def readlink(path): 
    """ Windows readlink implementation. """ 
    # This wouldn't return true if the file didn't exist, as far as I know. 
    assert islink(path) 
    assert type(path) == unicode 

    # Open the file correctly depending on the string type. 
    hfile = CreateFile(path, GENERIC_READ, 0, OPEN_EXISTING, 
         FILE_FLAG_REPARSE_BACKUP) 
    # MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 = (16*1024) 
    buffer = DeviceIoControl(hfile, FSCTL_GET_REPARSE_POINT, None, 16384) 
    CloseHandle(hfile) 

    # Minimum possible length (assuming length of the target is bigger than 0) 
    if not buffer or len(buffer) < 9: 
     return None 

    # Parse and return our result. 
    # typedef struct _REPARSE_DATA_BUFFER { 
    # ULONG ReparseTag; 
    # USHORT ReparseDataLength; 
    # USHORT Reserved; 
    # union { 
    #  struct { 
    #   USHORT SubstituteNameOffset; 
    #   USHORT SubstituteNameLength; 
    #   USHORT PrintNameOffset; 
    #   USHORT PrintNameLength; 
    #   ULONG Flags; 
    #   WCHAR PathBuffer[1]; 
    #  } SymbolicLinkReparseBuffer; 
    #  struct { 
    #   USHORT SubstituteNameOffset; 
    #   USHORT SubstituteNameLength; 
    #   USHORT PrintNameOffset; 
    #   USHORT PrintNameLength; 
    #   WCHAR PathBuffer[1]; 
    #  } MountPointReparseBuffer; 
    #  struct { 
    #   UCHAR DataBuffer[1]; 
    #  } GenericReparseBuffer; 
    # } DUMMYUNIONNAME; 
    # } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; 

    # Only handle SymbolicLinkReparseBuffer 
    (tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength, 
    PrintNameOffset, PrintNameLength, 
    Flags) = struct.unpack(SymbolicLinkReparseFormat, 
          buffer[:SymbolicLinkReparseSize]) 
    print tag, dataLength, reserver, SubstituteNameOffset, SubstituteNameLength 
    start = SubstituteNameOffset + SymbolicLinkReparseSize 
    actualPath = buffer[start : start + SubstituteNameLength].decode("utf-16") 
    # This utf-16 string is null terminated 
    index = actualPath.find(u"\0") 
    assert index > 0 
    if index > 0: 
     actualPath = actualPath[:index] 
    if actualPath.startswith(u"?\\"): 
     return actualPath[2:] 
    else: 
     return actualPath 
+0

그래, 재 파일 포인트가 유니 코드 문자열로 저장되었다는 것을 깨닫지 못했다. (어리석은 실수) 위의 코드 나 방금 최근에 발견 한 모듈 (https://github.com/sid0/ntfs)을 사용하는 것이 훨씬 낫습니다. –

4

에서 코드를 협의했습니다.

-1

여기에 내가 윈도우 XP SP3을에 CreateHardLinkA을 사용 http://www.geoffchappell.com/studies/windows/win32/kernel32/api/

KERNEL32.DLL

의 모든 메소드를 포함하는 링크, 그것은했다!

수입하는 ctypes os.path.exists (link_file) 경우 : os.remove (link_file)

dll = ctypes.windll.LoadLibrary("kernel32.dll") 
dll.CreateHardLinkA(link_file, _log_filename, 0) 
+0

Vista에서 소개 된 Symlink는 재분석 지점으로 구현되므로 POSIX의 심볼릭 링크와 가깝습니다. 하드 링크는 그 일과 관련이 없습니다. – 0xC0000022L

4

링크를 생성 서브 프로세스에 mklink 명령을 사용하여.

from subprocess import call 
call(['mklink', 'LINK', 'TARGET'], shell=True) 
+0

다른 솔루션에 문제가 있습니다.하지만 제대로 작동합니다. – Zitrax