python, windows: análisis de líneas de comando con shlex

cuando tiene que dividir una línea de comando, por ejemplo para llamar a popen, la mejor práctica parece ser

subprocess.Popen(shlex.split(cmd), ...

pero RTFM

La clase shlex facilita la escritura de analizadores léxicos para syntax simples que se asemejan a las del shell de Unix …

Entonces, ¿cuál es la forma correcta en win32? y ¿qué pasa con el análisis de cotización y POSIX vs modo no POSIX? Un cordial saludo, Massimo

Hasta ahora no hay una función de división de línea de comandos válida en la plataforma estándar de Python para Windows / multiplataforma. (Mar 2016)

subproceso

Así que, en resumen, para subprocess.Popen .call etc. es mejor hacer lo siguiente:

 if sys.platform == 'win32': args = cmd else: args = shlex.split(cmd) subprocess.Popen(args, ...) 

En Windows, la división no es necesaria para ninguno de los valores de la opción de shell y, internamente, Popen solo utiliza subprocess.list2cmdline para volver a unirse a los argumentos divididos :-).

Con la opción shell=True shlex.split tampoco es necesario en Unix.

Dividido o no, en Windows para iniciar los .bat o .cmd (a diferencia de .exe .com), debe incluir la extensión de archivo explícitamente, a menos que shell=True .

Notas sobre la división de la línea de comandos, sin embargo:

shlex.split(cmd, posix=0) retiene las barras invertidas en las rutas de Windows, pero no entiende las citas y el escape correcto. No está muy claro para qué sirve el modo posix = 0 de shlex, pero el 99% ciertamente seduce a los progtwigdores de Windows / multiplataforma …

La API de Windows expone ctypes.windll.shell32.CommandLineToArgvW :

Analiza una cadena de línea de comando Unicode y devuelve una matriz de punteros a los argumentos de la línea de comando, junto con un recuento de dichos argumentos, de manera similar a los valores estándar argv y argc en tiempo de ejecución de C.

 def win_CommandLineToArgvW(cmd): import ctypes nargs = ctypes.c_int() ctypes.windll.shell32.CommandLineToArgvW.restype = ctypes.POINTER(ctypes.c_wchar_p) lpargs = ctypes.windll.shell32.CommandLineToArgvW(unicode(cmd), ctypes.byref(nargs)) args = [lpargs[i] for i in range(nargs.value)] if ctypes.windll.kernel32.LocalFree(lpargs): raise AssertionError return args 

Sin embargo, esa función CommandLineToArgvW es falsa, o simplemente débilmente similar a la norma C argv & argc análisis obligatorio:

 >>> win_CommandLineToArgvW('aaa"bbb""" ccc') [u'aaa"bbb"""', u'ccc'] >>> win_CommandLineToArgvW('"" aaa"bbb""" ccc') [u'', u'aaabbb" ccc'] >>> 
 C:\scratch>python -c "import sys;print(sys.argv)" aaa"bbb""" ccc ['-c', 'aaabbb"', 'ccc'] C:\scratch>python -c "import sys;print(sys.argv)" "" aaa"bbb""" ccc ['-c', '', 'aaabbb"', 'ccc'] 

Vea http://bugs.python.org/issue1724822 para posibles adiciones futuras en Python lib. (La función mencionada en el servidor “fisheye3” realmente no funciona correctamente).


Función candidata multiplataforma

La división válida de la línea de comandos de Windows es bastante loca. Por ejemplo, intente \ \\ \" \\"" \\\"aaa """"

Mi función candidata actual para la división de línea de comando multiplataforma es la siguiente función que considero para la propuesta lib de Python. Su multiplataforma; es ~ 10 veces más rápido que shlex, que hace pasos y secuencias de un solo char; y también respeta los caracteres relacionados con la tubería (a diferencia de shlex). Se encuentra en una lista de pruebas de shell reales difíciles ya en Windows y Linux, además de los patrones de prueba posix heredados de test_shlex . Interesado en la retroalimentación sobre los errores restantes.

 def cmdline_split(s, platform='this'): """Multi-platform variant of shlex.split() for command-line splitting. For use with subprocess, for argv injection etc. Using fast REGEX. platform: 'this' = auto from current platform; 1 = POSIX; 0 = Windows/CMD (other values reserved) """ if platform == 'this': platform = (sys.platform != 'win32') if platform == 1: RE_CMD_LEX = r'''"((?:\\["\\]|[^"])*)"|'([^']*)'|(\\.)|(&&?|\|\|?|\d?\>|[<])|([^\s'"\\&|<>]+)|(\s+)|(.)''' elif platform == 0: RE_CMD_LEX = r'''"((?:""|\\["\\]|[^"])*)"?()|(\\\\(?=\\*")|\\")|(&&?|\|\|?|\d?>|[<])|([^\s"&|<>]+)|(\s+)|(.)''' else: raise AssertionError('unkown platform %r' % platform) args = [] accu = None # collects pieces of one arg for qs, qss, esc, pipe, word, white, fail in re.findall(RE_CMD_LEX, s): if word: pass # most frequent elif esc: word = esc[1] elif white or pipe: if accu is not None: args.append(accu) if pipe: args.append(pipe) accu = None continue elif fail: raise ValueError("invalid or incomplete shell string") elif qs: word = qs.replace('\\"', '"').replace('\\\\', '\\') if platform == 0: word = word.replace('""', '"') else: word = qss # may be even empty; must be last accu = (accu or '') + word if accu is not None: args.append(accu) return args