Симлинки на windows? - PullRequest
       27

Симлинки на windows?

36 голосов
/ 19 сентября 2009

Кто-нибудь знает способ создания / чтения символических ссылок между версиями win32 из Python? В идеале должно быть минимальное количество кода для конкретной платформы, так как мне нужно, чтобы мое приложение было межплатформенным.

Ответы [ 10 ]

37 голосов
/ 19 сентября 2009

Файловая система 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

Кроме того : Это также работает с каталогами (укажите это с третьим аргументом). С поддержкой юникода это выглядит так:

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

см. Также Создание точки соединения NTFS в Python

12 голосов
/ 28 октября 2011

расширение python ntfslink

Или, если вы хотите использовать 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 +.

11 голосов
/ 19 сентября 2009

Проблема, как объяснено, например, здесь , что собственная поддержка Windows функциональности символических ссылок различается в разных выпусках Windows, так что, например, в Vista (с большим количеством работы) вы можете получить больше функциональности, чем в XP или 2000 (ничто не AFAIK на других версиях win32). Или вместо этого вы можете использовать ярлыки, которые, конечно, имеют свои собственные ограничения и не являются «действительно» эквивалентными символическим ссылкам Unix. Таким образом, вы должны точно указать, какие функциональные возможности вам требуются, сколько из них вы готовы пожертвовать на алтаре кросс-win32-операции и т. Д. - ТОГДА мы можем решить, как реализовать компромисс, который вы выбрали с точки зрения из ctypes или win32all звонков ... это, в некотором смысле, меньшее из них.

10 голосов
/ 11 декабря 2013

os.symlink работает на Python 3.3 с использованием Windows 8.1 с файловой системой NTFS.

9 голосов
/ 06 марта 2014

Используя команду mklink в подпроцессе, создайте ссылку.

from subprocess import call
call(['mklink', 'LINK', 'TARGET'], shell=True)
7 голосов
/ 08 декабря 2010

Я помещаю следующее в 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
1 голос
/ 01 мая 2012

Код Джунталиса не обрабатывает Unicode, поэтому я изменил его для использования ctypes, а также упростил его с помощью struct. Я также ознакомился с кодом из Использование структуры в качестве аргумента функции с модулем Python ctypes

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 голосов
/ 15 мая 2019

При попытке создать символическую ссылку в 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'

0 голосов
/ 11 мая 2019

Как уже упоминалось в другом ответе, использование subprocess.call, вероятно, является лучшим вариантом для окон. Однако прямой вызов mklink может привести к:

[WinError 2] Системе не удается найти указанный файл

В Windows Server 2016 мне удалось настроить следующие файлы для работы с файлами:

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

Измените переключатели, указанные выше, в соответствии с документацией mklink.

0 голосов
/ 07 февраля 2014

вот ссылка, содержащая все методы kernel32.dll

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

Я использовал CreateHardLinkA на Windows XP SP3, все заработало!

импортные типы если os.path.exists (link_file): os.remove (link_file)

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