Почему subprocess.Popen() с shell= True работает по-разному в Linux и Windows?
-
12-09-2019 - |
Вопрос
При использовании subprocess.Popen(args, shell=True)
бежать "gcc --version
" (просто в качестве примера), в Windows мы получаем это:
>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc (GCC) 3.4.5 (mingw-vista special r3) ...
Так что это прекрасно распечатывает версию, как я и ожидал.Но в Linux мы получаем это:
>>> from subprocess import Popen
>>> Popen(['gcc', '--version'], shell=True)
gcc: no input files
Поскольку gcc не получил --version
вариант.
В документах не указано точно, что должно произойти с аргументами в Windows, но там сказано, что в Unix, "Если args представляет собой последовательность, то первый элемент задает командную строку, и любые дополнительные элементы будут обрабатываться как дополнительные аргументы оболочки". ИМХО, способ Windows лучше, потому что он позволяет вам лечить Popen(arglist)
вызывает то же самое , что и Popen(arglist, shell=True)
единицы.
В чем разница между Windows и Linux здесь?
Решение
На самом деле в Windows он использует cmd.exe
когда shell=True
- это предваряет cmd.exe /c
(на самом деле он просматривает COMSPEC
переменная окружения, но по умолчанию имеет значение cmd.exe
если отсутствует) к аргументам оболочки.(В Windows 95/98 он использует промежуточный w9xpopen
программа для фактического запуска команды).
Таким образом, странная реализация на самом деле является UNIX
один, который выполняет следующее (где каждый пробел разделяет другой аргумент):
/bin/sh -c gcc --version
Похоже, что правильная реализация (по крайней мере, в Linux) была бы:
/bin/sh -c "gcc --version" gcc --version
Поскольку это установило бы командную строку из параметров, указанных в кавычках, и успешно передало бы другие параметры.
Из sh
раздел справочной страницы для -c
:
Read commands from the command_string operand instead of from the standard input. Special parameter 0 will be set from the command_name operand and the positional parameters ($1, $2, etc.) set from the remaining argument operands.
Этот патч, кажется, довольно просто делает свое дело:
--- subprocess.py.orig 2009-04-19 04:43:42.000000000 +0200
+++ subprocess.py 2009-08-10 13:08:48.000000000 +0200
@@ -990,7 +990,7 @@
args = list(args)
if shell:
- args = ["/bin/sh", "-c"] + args
+ args = ["/bin/sh", "-c"] + [" ".join(args)] + args
if executable is None:
executable = args[0]
Другие советы
Из subprocess.py источника:
В UNIX, с shell=True:Если аргументы представляют собой строку, они указывают командную строку для выполнения через командную оболочку.Если аргументы представляют собой последовательность, первый элемент задает командную строку, а любые дополнительные элементы будут рассматриваться как дополнительные аргументы командной строки.
В Windows:класс Popen использует CreateProcess() для выполнения дочерней программы программа, которая работает со строками.Если аргументы представляют собой последовательность, они будут преобразованы в строку с использованием метода list2cmdline.Пожалуйста, обратите внимание, что не все приложения MS Windows интерпретируют командную строку одинаково способ:Строка list2cmd предназначена для приложений, использующих те же правила , что и среда выполнения MS C.
Это не отвечает на вопрос "почему", просто разъясняет, что вы видите ожидаемое поведение.
"Почему", вероятно, заключается в том, что в UNIX-подобных системах аргументы команды фактически передаются приложениям (используя exec*
семейство вызовов) в виде массива строк.Другими словами, вызывающий процесс решает, что входит в КАЖДЫЙ аргумент командной строки.Принимая во внимание, что когда вы указываете ему использовать оболочку, вызывающий процесс фактически получает возможность передать оболочке для выполнения только один аргумент командной строки:Вся командная строка, которую вы хотите выполнить, имя исполняемого файла и аргументы, в виде одной строки.
Но в Windows вся командная строка (согласно приведенной выше документации) передается дочернему процессу в виде одной строки.Если вы посмотрите на Процесс создания Документации API, вы заметите, что он ожидает, что все аргументы командной строки будут объединены вместе в большую строку (отсюда и вызов list2cmdline
).
Плюс есть тот факт, что в UNIX-подобных системах на самом деле является оболочка, которая может делать полезные вещи, поэтому я подозреваю, что другая причина разницы заключается в том, что в Windows, shell=True
ничего не делает, вот почему это работает так, как вы видите.Единственный способ заставить две системы действовать идентично - это просто удалить все аргументы командной строки, когда shell=True
в Windows.
Причина поведения UNIX в shell=True
это связано с цитированием.Когда мы пишем команду оболочки, она будет разделена пробелами, поэтому нам придется привести некоторые аргументы:
cp "My File" "New Location"
Это приводит к проблемам, когда наши аргументы содержать кавычки, для которых требуется экранирование:
grep -r "\"hello\"" .
Иногда мы можем получить ужасные ситуации где \
должно быть, тоже сбежал!
Конечно, настоящая проблема заключается в том, что мы пытаемся использовать один строка для указания множественный струны.При вызове системных команд большинство языков программирования избегают этого, позволяя нам в первую очередь отправлять несколько строк, следовательно:
Popen(['cp', 'My File', 'New Location'])
Popen(['grep', '-r', '"hello"'])
Иногда бывает неплохо запускать "необработанные" команды оболочки;например, если мы копируем-вставляем что-то из сценария командной строки или веб-сайта, и мы не хотим преобразовывать все ужасные экранирования вручную.Вот почему shell=True
опция существует:
Popen(['cp "My File" "New Location"'], shell=True)
Popen(['grep -r "\"hello\"" .'], shell=True)
Я не знаком с Windows, поэтому не знаю, как и почему она ведет себя по-другому.