Convierta POSIX-> WIN, en Cygwin Python, sin llamar a cygpath

Utilizo un script de Python, que se ejecuta en una comstackción Cygwin de Python, para crear comandos emitidos a las utilidades nativas de Windows (no es compatible con Cygwin). Esto requiere la conversión de los parámetros de ruta de POSIX a WIN antes de emitir el comando.

Llamar a la utilidad cygpath es la mejor manera de hacer esto, ya que usa a Cygwin para hacer lo que tiene que hacer, pero también es un poco horrible (y lento).

Ya estoy ejecutando una comstackción Cygwin de Python, por lo que el código para hacer la conversión está presente. Parece que debería haber una extensión específica de Cygwin / Python que me enganche a esta capacidad, directamente en Python, sin tener que iniciar un proceso completamente nuevo.

Esto es posible llamando a la API de Cygwin usando ctypes. El siguiente código funciona para mí: estoy usando la versión 2.5.2 de cygwin DLL de 64 bits en Windows 2012, y esto funciona en las versiones Cygwin de Python 2.7.10 y Python 3.4.3.

Básicamente, llamamos a cygwin_create_path desde cygwin1.dll para realizar la conversión de ruta. Esa función asigna un búfer de memoria (usando malloc ) que contiene la ruta convertida. Entonces necesitamos usar free de cygwin1.dll para liberar el búfer que asignó.

Tenga en cuenta que el xunicode continuación es la alternativa de un hombre pobre a seis (una biblioteca de compatibilidad de Python 2/3); Si necesita admitir Python 2 y 3, seis es la mejor respuesta, pero quería que mi ejemplo estuviera libre de dependencias en cualquier módulo no incluido, por eso lo hice de esta manera.

 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 (ie 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) 

Al explorar la fuente de cygpath , parece que cygpath tiene una implementación no trivial y no tiene disponible ninguna versión de biblioteca.

cygpath admite tomar su entrada desde un archivo usando la opción -f (o desde stdin, usando -f - ) y puede tomar múltiples rutas, escupiendo una ruta convertida cada vez, por lo que probablemente podría crear una única instancia abierta de cygpath (usando Subprocess.Popen de Python) en lugar de reiniciar cygpath cada vez.

Preferiría escribir este ayudante de Python que usa el dll cygwin :

 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) 

Me encontré con este problema de forma independiente recientemente. La solución pequeña y rápida que se me ocurrió es la siguiente:

 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) 

Esto funciona con las rutas de estilo cygwin ( /cygdrive/... ) y MinGW ( /... ) (tuve que admitir ambas) así como con las rutas relativas.

 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)) 

Produce:

 C:\test\path C:\test\path .\test\path ..\test\path C:\Windows\Path .\Windows\Path ..\Windows\Path