Преобразовать POSIX-> путь WIN, в Cygwin Python, без вызова cygpath
Я использую Python script, работающий в Cygwin-сборке Python, для создания команд, выпущенных для родных утилит Windows (не Cygwin-aware). Это требует преобразования параметров пути из POSIX в форму WIN перед выдачей команды.
Вызов утилиты cygpath - это лучший способ сделать это, поскольку он использует Cygwin для выполнения того, что он должен делать, но также немного ужасающий (и медленный).
Я уже запускаю Cygwin-сборку Python - так что код для преобразования присутствует. Похоже, что должно существовать специальное расширение Cygwin/Python, которое дает мне возможность подключиться к этой возможности прямо на Python без необходимости запуска совершенно нового процесса.
Ответы
Ответ 1
Это возможно, вызывая API Cygwin с помощью ctypes. Следующий код работает для меня - я использую 64-разрядную версию cygwin DLL версии 2.5.2 в Windows 2012, и это работает на версиях Cygwin как Python 2.7.10, так и Python 3.4.3.
В основном мы вызываем cygwin_create_path
из cygwin1.dll
для выполнения преобразования пути. Эта функция выделяет буфер памяти (используя malloc
), содержащий преобразованный путь. Итак, нам нужно использовать free
из cygwin1.dll
, чтобы освободить выделенный буфер.
Обратите внимание, что xunicode
ниже - это бедный человек, альтернативный six (библиотека совместимости Python 2/3); если вам нужно поддерживать оба Python 2 и 3, шесть - лучший ответ, но я хотел, чтобы мой пример был свободен от зависимостей от любых несвязанных модулей, поэтому я сделал это таким образом.
from ctypes import cdll, c_void_p, c_int32, cast, c_char_p, c_wchar_p
from sys import version_info
xunicode = str if version_info[0] > 2 else eval("unicode")
# If running under Cygwin Python, just use DLL name
# If running under non-Cygwin Windows Python, use full path to cygwin1.dll
# Note Python and cygwin1.dll must match bitness (i.e. 32-bit Python must
# use 32-bit cygwin1.dll, 64-bit Python must use 64-bit cygwin1.dll.)
cygwin = cdll.LoadLibrary("cygwin1.dll")
cygwin_create_path = cygwin.cygwin_create_path
cygwin_create_path.restype = c_void_p
cygwin_create_path.argtypes = [c_int32, c_void_p]
# Initialise the cygwin DLL. This step should only be done if using
# non-Cygwin Python. If you are using Cygwin Python don't do this because
# it has already been done for you.
cygwin_dll_init = cygwin.cygwin_dll_init
cygwin_dll_init.restype = None
cygwin_dll_init.argtypes = []
cygwin_dll_init()
free = cygwin.free
free.restype = None
free.argtypes = [c_void_p]
CCP_POSIX_TO_WIN_A = 0
CCP_POSIX_TO_WIN_W = 1
CCP_WIN_A_TO_POSIX = 2
CCP_WIN_W_TO_POSIX = 3
def win2posix(path):
"""Convert a Windows path to a Cygwin path"""
result = cygwin_create_path(CCP_WIN_W_TO_POSIX,xunicode(path))
if result is None:
raise Exception("cygwin_create_path failed")
value = cast(result,c_char_p).value
free(result)
return value
def posix2win(path):
"""Convert a Cygwin path to a Windows path"""
result = cygwin_create_path(CCP_POSIX_TO_WIN_W,str(path))
if result is None:
raise Exception("cygwin_create_path failed")
value = cast(result,c_wchar_p).value
free(result)
return value
# Example, convert LOCALAPPDATA to cygwin path and back
from os import environ
localAppData = environ["LOCALAPPDATA"]
print("Original Win32 path: %s" % localAppData)
localAppData = win2posix(localAppData)
print("As a POSIX path: %s" % localAppData)
localAppData = posix2win(localAppData)
print("Back to a Windows path: %s" % localAppData)
Ответ 2
Из просмотра источника cygpath, похоже, что cygpath имеет нетривиальную реализацию и не делает доступной версию библиотеки.
cygpath поддерживает ввод своих данных из файла с помощью параметра -f
(или из stdin, используя -f -
), и может принимать несколько путей, каждый раз выплескивая преобразованный путь, так что вы, вероятно, могли бы создать единый cygpath instance open (используя Python subprocess.Popen), а не перезапускать cygpath каждый раз.
Ответ 3
Я бы предпочел написать этот помощник Python, который использует cygwin
dll:
import errno
import ctypes
import enum
import sys
class ccp_what(enum.Enum):
posix_to_win_a = 0 # from is char *posix, to is char *win32
posix_to_win_w = 1 # from is char *posix, to is wchar_t *win32
win_a_to_posix = 2 # from is char *win32, to is char *posix
win_w_to_posix = 3 # from is wchar_t *win32, to is char *posix
convtype_mask = 3
absolute = 0 # Request absolute path (default).
relative = 0x100 # Request to keep path relative.
proc_cygdrive = 0x200 # Request to return /proc/cygdrive path (only with CCP_*_TO_POSIX)
class CygpathError(Exception):
def __init__(self, errno, msg=""):
self.errno = errno
super(Exception, self).__init__(os.strerror(errno))
class Cygpath(object):
bufsize = 512
def __init__(self):
if 'cygwin' not in sys.platform:
raise SystemError('Not running on cygwin')
self._dll = ctypes.cdll.LoadLibrary("cygwin1.dll")
def _cygwin_conv_path(self, what, path, size = None):
if size is None:
size = self.bufsize
out = ctypes.create_string_buffer(size)
ret = self._dll.cygwin_conv_path(what, path, out, size)
if ret < 0:
raise CygpathError(ctypes.get_errno())
return out.value
def posix2win(self, path, relative=False):
out = ctypes.create_string_buffer(self.bufsize)
t = ccp_what.relative.value if relative else ccp_what.absolute.value
what = ccp_what.posix_to_win_a.value | t
return self._cygwin_conv_path(what, path)
def win2posix(self, path, relative=False):
out = ctypes.create_string_buffer(self.bufsize)
t = ccp_what.relative.value if relative else ccp_what.absolute.value
what = ccp_what.win_a_to_posix.value | t
return self._cygwin_conv_path(what, path)
Ответ 4
Я недавно столкнулся с этой проблемой самостоятельно. Небольшое и быстрое решение, которое я нашел, заключается в следующем:
import os
import re
def win_path(path):
match = re.match('(/(cygdrive/)?)(.*)', path)
if not match:
return path.replace('/', '\\')
dirs = match.group(3).split('/')
dirs[0] = f'{dirs[0].upper()}:'
return '\\'.join(dirs)
Это работает как с путями в стиле cygwin (/cygdrive/...
), так и с MinGW (/...
) (я должен был поддерживать оба), а также с относительными путями.
l = ['/c/test/path',
'/cygdrive/c/test/path',
'./test/path',
'../test/path',
'C:\Windows\Path',
'.\Windows\Path',
'..\Windows\Path']
for i in l:
print(win_path(i))
Производит:
C:\test\path
C:\test\path
.\test\path
..\test\path
C:\Windows\Path
.\Windows\Path
..\Windows\Path