【问题标题】:Symlinks on windows?Windows上的符号链接?
【发布时间】:2010-11-29 16:10:40
【问题描述】:

有谁知道从 Python 跨 win32 版本创建/读取符号链接的方法?理想情况下,应该有最少数量的平台特定代码,因为我需要我的应用程序是跨平台的。

【问题讨论】:

  • 我需要的功能是能够创建一个目录,其中包含指向文件系统中不同位置的文件的链接,然后让我的 Python 代码能够像打开这些文件一样打开这些文件目录。
  • 为什么需要这个功能?
  • 为什么不直接使用os.symlink?在 Windows 10 上为我工作(需要以管理员身份运行)。

标签: python winapi symlink pywin32


【解决方案1】:

NTFS 文件系统有连接点,我想你可以使用它们,你可以使用 python win32 API 模块,例如

import win32file

win32file.CreateSymbolicLink(fileSrc, fileTarget, 1)

如果你不想依赖win32API模块,你可以随时使用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) 说最低支持的客户端是 Windows Vista

另外:这也适用于目录(用第三个参数表示)。有了 unicode 支持,它看起来像这样:

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

另见 Create NTFS junction point in Python

【讨论】:

  • 你不应该使用..A Windows API,而是使用..W (Unicode) - 每次 - 即使在这样的例子中。
  • win32file.CreateSymbolicLink 显示的参数名称有点混乱。对于那些想知道的人,第一个是要创建的链接的名称,第二个是它应该链接到的路径。
  • 为了清楚起见:win32file.CreateSymboleLink 上的最后一个参数应该是 1 表示目录,0 表示文件。
  • 每个支持此功能的 Windows 版本都会将kernel32.dll 加载到进程中。无需明确说明。
  • 澄清一下:这会创建一个符号链接,而不是一个连接点
【解决方案2】:

os.symlink 在 Python 3.3 上使用 Windows 8.1 和 NTFS 文件系统。

【讨论】:

  • 不用于目录链接,但是没关系
  • 但它需要管理员权限:“WinError 1314: 客户端不拥有所需的权限。”
  • 我认为没有办法解决这个问题 - 这是一个操作系统限制
【解决方案3】:

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 中的属性,但对于正常情况,它应该可以工作。随意改进它。

如果您使用 MOUNTPOINT 而不是 SYMBOLIC_LINK,它也应该适用于文件夹连接。

你可以检查一下

sys.getwindowsversion()[0] >= 6

如果你把它放到你要发布的东西中,因为这种形式的符号链接只在 Vista+ 上受支持。

【讨论】:

  • 我对此提交了一个编辑,但它被拒绝了,即使实际上存在一个错误。在 islink() 中,必须将掩码值与掩码进行比较以避免误报(零 = 假/非零 = 真快捷方式仅适用于单个位掩码,例如 1024)。否则,普通目录也被标识为“链接”。该行应为“如果 GetFileAttributes(fpath) & REPARSE_FOLDER == REPARSE_FOLDER:”
  • 如果我确实拒绝了它,我一定是错误地这样做了,对此我深表歉意。 (老实说,我不太确定我什至会去哪里查看编辑请求,因为我从来没有在这里收到过)。不过,我继续更新了你的修复,所以谢谢你。
  • 哦,不,没有拒绝任何东西……另外两个编辑拒绝了,这很奇怪,因为我建议的编辑只是纠正了错误。也就是说,再次感谢您的帖子 - 它非常有帮助,值得深入研究! :-)
  • 请记住,如果路径不存在,islink 的此实现将返回 True
【解决方案4】:

在子进程中使用 mklink 命令创建链接。

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

【讨论】:

  • 我在使用其他解决方案时遇到了问题 - 但这很好用。
【解决方案5】:

问题是,如解释,例如here,Windows 自己对符号链接功能的支持因 Windows 版本而异,例如在 Vista(有很多工作)中,您可以获得比 XP 或 2000 更多的功能(在其他 win32 版本上没有 AFAIK)。或者您可以使用快捷方式,它们当然有自己的一组限制,并且并不“真正”等同于 Unix 符号链接。因此,您必须准确指定您需要哪些功能,您愿意在跨 Win32 操作的祭坛上牺牲多少功能等等——然后,我们可以制定出如何实现您选择的折衷方案ctypeswin32all 的电话……从某种意义上说,这是最少的。

【讨论】:

    【解决方案6】:

    我将以下内容放入 Lib/site-packages/sitecustomize.py

    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
    

    【讨论】:

      【解决方案7】:

      Juntalis 的代码不处理 Unicode,所以我将其修改为使用 ctypes 并使用 struct 对其进行了简化。我还查阅了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
      

      【讨论】:

      • 是的,在撰写本文时并没有意识到重解析点存储为 unicode 字符串。 (当然是愚蠢的错误)你最好使用上面的代码,或者我最近发现的一个模块on github
      【解决方案8】:

      正如另一个答案中提到的,使用 subprocess.call 可能是 Windows 的最佳选择。但是直接调用 'mklink' 可能会导致:

      [WinError 2] 系统找不到指定的文件

      在 Windows Server 2016 上,我能够使以下内容适用于文件:

      import subprocess
      subprocess.call(['cmd', '/c', 'mklink', '<path_for_symlink>', '<path_for_file>'])
      

      根据 mklink 文档更改上面的开关。

      【讨论】:

        【解决方案9】:

        尝试在 Windows 中创建符号链接我总是得到错误

        A required privilege is not held by the client
        

        但是我在使用此代码创建快捷方式时成功

        import win32com.client
        import pythoncom
        import os
        
        def create_shortcut(original_filepath, shortcut_filepath):
            shell = win32com.client.Dispatch("WScript.Shell")
            shortcut = shell.CreateShortCut(shortcut_filepath)
            shortcut.Targetpath = original_filepath
            shortcut.WindowStyle = 7
            shortcut.save()
        
        create_shortcut(r'C:\Users\xxx\Desktop\test.jpg', 
                        r'C:\Users\xxx\Desktop\test.lnk')
        

        注意:确保快捷方式以“.lnk”结尾

        【讨论】:

          【解决方案10】:

          这里是包含 kernel32.dll 的所有方法的链接

          http://www.geoffchappell.com/studies/windows/win32/kernel32/api/

          我在 Windows xp sp3 上使用了 CreateHardLinkA,它成功了!

          导入 ctypes 如果 os.path.exists(link_file): os.remove(link_file)

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

          【讨论】:

          • 在 Vista 中引入的符号链接被实现为重解析点,因此更接近 POSIX 中的符号链接。硬链接与此无关。
          猜你喜欢
          • 2014-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-05
          • 2011-08-20
          • 2012-04-28
          • 2011-03-23
          • 2012-02-01
          相关资源
          最近更新 更多