Почему вызов подпроцесса Python не работает правильно с sh в Windows? - PullRequest
3 голосов
/ 04 ноября 2019

Следующий код печатает hi:

import subprocess
subprocess.call(['sh', '-c', ' "$@" ', '-', 'echo', 'hi'])

Однако в родном Windows Python небольшое изменение кода:

import subprocess
subprocess.call(['sh', '-c',  '"$@"' , '-', 'echo', 'hi'])

приводит к следующим ошибкам:

sh: -c: line 0: unexpected EOF while looking for matching `"'
sh: -c: line 1: syntax error: unexpected end of file

Почему это?

1 Ответ

3 голосов
/ 04 ноября 2019

Это, похоже, удивительно давняя ошибка в MSYS2 (вероятно, от Cygwin), вызванная особенностью Windows цитированием правил . Что происходит, так это то, что MSYS2 ожидает, что

subprocess.list2cmdline(['sh', '-c',  '"$@"' , '-', 'echo', 'hi'])

переведет в

sh -c "\"$@\"" - echo hi

, но на самом деле выдает следующее:

sh -c \"$@\" - echo hi

Трудно понять, почемудо тех пор, пока вы не поймете, что MSYS2 считает, что правила цитирования в командной строке Windows таковы, что обратные слеши обрабатываются как литералы вне двойных кавычек.
В итоге получается, что \"$@\" получаетинтерпретируется как одиночная обратная косая черта, за которой следует строка $@\" в кавычках, конечная кавычка которой отсутствует . Если бы мы добавили заключительную кавычку, она бы фактически выглядела как \"$@\"", что кажется несбалансированным, но на самом деле сбалансировано с MSYS2. (!)
Однако, когда аргумент содержит пробел, все это наивно цитируется, случайно маскируя проблему.

Почему он интерпретирует вещи таким образом? Вероятно, это связано с документацией, в которой написано :

CommandLineToArgvW имеет специальную интерпретацию символов обратной косой черты, когда за ними следует символ кавычки ("). Эта интерпретация предполагает, что любой предыдущий аргумент является допустимым путем к файловой системе, иначе он может вести себя непредсказуемо.

Эта специальная интерпретация управляет режимом "в кавычках" , отслеживаемым анализатором.

Довольно просто и соблазнительно неправильно это понять и думать, что обратные слэши теряют свое особое значение, когда анализатор не в режиме «в кавычках», и это то, что говорит анализатор MSYS2. Однако, если вы внимательно прочитаете следующие два предложения, это объясняет, что именно означает «in-quotes» :

Когда этот режим отключен, пробел завершает текущий аргумент. Когда включено, пробел добавляется к аргументу, как и все другие символы.

То есть all . Обратные слеши не внезапно становятся дословными за пределами кавычек. Они по-прежнему могут избегать кавычек, как если бы они были внутри, за исключением того, что, конечно, правила даже более сложнее внутри, чем снаружи.

Как вы обходите это? К счастью, родной Windows Python позволяет передавать всю командную строку в виде однострочного литерала, так что вы можете обойти эту ошибку, используя вспомогательный метод:

import subprocess
def list2cmdline(args): return ' '.join(map(
    lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
    map(lambda a: subprocess.list2cmdline([a]), args)))

subprocess.call(list2cmdline(['sh', '-c', '"$@"', '-', 'echo', 'hi']))

В качестве альтернативы, вы можете напрямую подключить его к обезьяне:

import subprocess
subprocess.list2cmdline = (lambda old: lambda args: ' '.join(map(
    lambda a: a if a.lstrip().startswith('"') or '"' not in a else '"' + a + '"',
    map(lambda a: old([a]), args))))(subprocess.list2cmdline)

subprocess.call(['sh', '-c', '"$@"', '-', 'echo', 'hi'])

Это не должно влиять на любые программы с корректным поведением, потому что есть несколько способов заключать в кавычки, но это должно решить проблему с MSYS2.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...