Кто-нибудь знает способ сделать/прочитать символические ссылки по версиям win32 из Python? В идеале должен быть минимальный код конкретной платформы, так как мне нужно, чтобы мое приложение было кросс-платформой.
Символы на окнах?
Ответ 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
Кроме того: Это также работает с каталогами (укажите это с третьим аргументом). С поддержкой юникода это выглядит так:
kdll.CreateSymbolicLinkW(UR"D:\testdirLink", UR"D:\testdir", 1)
Ответ 2
Или, если вы хотите использовать 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 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 +.
Ответ 3
Проблема, как объяснено, например, здесь, что собственная поддержка Windows для функциональных возможностей символических ссылок зависит от версий Windows, так что, например, в Vista (с большим количеством работы) вы можете получить больше функциональности, чем в XP или 2000 (ничего AFAIK в других версиях win32). Или у вас могут быть ярлыки вместо этого, которые, конечно же, имеют свой собственный набор ограничений и не "действительно" эквивалент символических ссылок Unix. Итак, вы должны точно указать, какие функции вы требуете, сколько из тех, кого вы готовы жертвовать на алтаре кросс-win32-операции и т.д. - ТОГДА мы можем решить, как реализовать компромисс, который вы выбрали в терминах из ctypes
или win32all
вызывает... что наименьшее из этого, в некотором смысле.
Ответ 4
os.symlink работает на Python 3.3 с использованием Windows 8.1 с файловой системой NTFS.
Ответ 5
Использование команды mklink в подпроцессе создать ссылку.
from subprocess import call
call(['mklink', 'LINK', 'TARGET'], shell=True)
Ответ 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. Я также консультировался с кодом Использование структуры как аргумент функции с модулем ctypes python
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
Ответ 8
Как уже упоминалось в другом ответе, использование subprocess.call, вероятно, является лучшим вариантом для окон. Однако прямой вызов 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/
Я использовал CreateHardLinkA для Windows xp sp3, он сработает!
импорт ctypes если os.path.exists(link_file): os.remove(link_file)
dll = ctypes.windll.LoadLibrary("kernel32.dll")
dll.CreateHardLinkA(link_file, _log_filename, 0)