Почему subprocess.Popen() с shell= True работает по-разному в Linux и Windows?

StackOverflow https://stackoverflow.com/questions/1253122

Вопрос

При использовании 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, поэтому не знаю, как и почему она ведет себя по-другому.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top