【问题标题】:Convert POSIX->WIN path, in Cygwin Python, w/o calling cygpath转换 POSIX->WIN 路径,在 Cygwin Python 中,不调用 cygpath
【发布时间】:2012-06-08 16:49:35
【问题描述】:

我使用 Python 脚本,在 Python 的 Cygwin 版本中运行,以创建发布给本地 Windows 实用程序(不支持 Cygwin)的命令。这需要在发出命令之前将路径参数从 POSIX 转换为 WIN 形式。

调用 cygpath 实用程序是执行此操作的最佳方法,因为它使用 Cygwin 来完成它可以做的事情,但它也有点可怕(而且速度很慢)。

我已经在运行 Python 的 Cygwin 版本 - 因此存在进行转换的代码。似乎应该有一个 Cygwin/Python 特定的扩展,让我可以直接在 Python 中使用此功能,而无需启动一个全新的进程。

【问题讨论】:

  • 我意识到问题是 not 调用 cygpath,但对于来自 Google 的任何人:在 subprocess.run 中调用 cygpath.exe 对我来说没有任何答案来了。

标签: python cygwin


【解决方案1】:

这可以通过使用 ctypes 调用 Cygwin API 来实现。以下代码适用于我——我在 Windows 2012 上使用 64 位 cygwin DLL 版本 2.5.2,这适用于 Python 2.7.10 和 Python 3.4.3 的 Cygwin 版本。

基本上我们从cygwin1.dll调用cygwin_create_path来执行路径转换。该函数分配一个包含转换后路径的内存缓冲区(使用malloc)。那么我们需要使用cygwin1.dll中的free来释放它分配的缓冲区。

请注意,下面的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)

【讨论】:

  • 感谢您分享 ctypes 代码,但我无法使其与 WinPython-3.5.2 和 2.7.10 一起运行:PY2 在 ctypes self._handle = _dlopen(self._name, mode) 上崩溃,PY3 在 result = cygwin_create_path(CCP_WIN_W_TO_POSIX, xunicode(path)) OSError: exception: access violation writing 0x0000000000000000 上崩溃。有什么想法吗?
  • @ankostis,我认为问题可能出在cygwin = cdll.LoadLibrary("cygwin1.dll") 这一行。使用 Cygwin Python,cygwin1.dll 已经加载到 Python 进程地址空间中,因此LoadLibrary 可以轻松找到它。但是,由于 WinPython 是纯 Windows 应用程序(没有 Cygwin),所以 cygwin1.dll 通常不会被加载,因此您需要提供您的 cygwin1.dll 的完整路径。另外,如果你的WinPython是64位的,你需要加载一个64位的cygwin1.dll;相反,如果你的 WinPython 是 32 位的,则需要加载一个 32 位的cygwin1.dll
  • 我按照你说的在另一台机器上试过了,现在我得到了这个:Original Win32 path: C:\Users\ankostis\AppData\Local 0 [main] python 772 D:\Apps\WinPython-64bit-3.5.2.1\python-3.5.2.amd64\python.exe: *** fatal error - Internal error: TP_NUM_C_BUFS too small: 50 1090 [main] python 772 cygwin_exception::open_stackdumpfile: Dumping stack trace to python.exe.stackdump @tokoti(cygwin):~/Work/gitdb.git$less python.exe.stackdump
  • @ankostis,我认为这是因为您需要先致电cygwin_dll_init。我已更新我的答案以包含该步骤。请注意,我最初不必这样做,因为我使用的是 Cygwin Python,而在 Cygwin Python 中,cygwin DLL 已经初始化。
  • 在 Cygwin 下,您应该能够捕获 errno 以获得更好的异常。使用cygwin = ctypes.CDLL("cygwin1.dll", use_errno=True)。然后,如果调用失败,则获取值为errno = ctypes.get_errno(),然后是raise OSError(errno, os.strerror(errno))。这在 Windows Python 中不起作用,因为它保存的 errno 值将用于 Python 的 C 运行时,而不是 Cygwin。
【解决方案2】:

通过浏览cygpath source,看起来 cygpath 有一个非平凡的实现,并且没有提供任何库版本。

cygpath 确实支持使用-f 选项(或从标准输入,使用-f -)从文件中获取其输入,并且可以采用多个路径,每次都吐出转换后的路径,因此您可以创建单个 cygpath实例打开(使用 Python 的 subprocess.Popen)而不是每次都重新启动 cygpath。

【讨论】:

    【解决方案3】:

    我宁愿编写这个使用cygwin dll 的 Python 助手:

    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
      

      【讨论】:

      • 您能否更新答案并用旧格式替换 f-Strings 用法?即,将dirs[0] = f'{dirs[0].upper()}:' 更改为dirs[0] = "%s:" % dirs[0].upper() 将允许它与早于3.6 的Python 版本兼容
      猜你喜欢
      • 2019-06-11
      • 1970-01-01
      • 1970-01-01
      • 2012-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-16
      • 2019-08-18
      相关资源
      最近更新 更多