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

При использовании 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 говорится: "Если аргументы - это последовательность, первый элемент задает командную строку, а любые дополнительные элементы будут следует рассматривать как дополнительные аргументы оболочки. " ИМХО способ Windows лучше, потому что он позволяет обрабатывать Popen(arglist) вызовов так же, как Popen(arglist, shell=True).

Почему здесь разница между Windows и Linux?

Ответы [ 3 ]

14 голосов
/ 10 августа 2009

На самом деле в 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]
5 голосов
/ 10 августа 2009

Из источника subprocess.py:

В UNIX с shell = True: если args является строкой, она указывает Командная строка для выполнения через оболочку. Если args является последовательностью, первый элемент указывает командную строку и любые дополнительные элементы будет рассматриваться как дополнительные аргументы оболочки.

В Windows: класс Popen использует CreateProcess () для выполнения дочернего элемента Программа, которая работает на строки. Если args - последовательность, это будет преобразован в строку с помощью метода list2cmdline. Обратите внимание, что не все приложения MS Windows интерпретируют командную строку одинаково способ: list2cmdline предназначен для приложений, использующих тот же правила как среда выполнения MS C.

Это не отвечает почему, просто разъясняет, что вы видите ожидаемое поведение.

«Почему», вероятно, заключается в том, что в UNIX-подобных системах аргументы команд фактически передаются приложениям (используя семейство вызовов exec*) в виде массива строк. Другими словами, вызывающий процесс решает, что входит в КАЖДЫЙ аргумент командной строки. Принимая во внимание, что когда вы указываете ему использовать оболочку, вызывающий процесс фактически получает только шанс передать один аргумент командной строки в оболочку для выполнения: вся требуемая командная строка, имя исполняемого файла и аргументы в виде одной строки.

Но в Windows вся командная строка (согласно приведенной выше документации) передается в виде одной строки дочернему процессу. Если вы посмотрите на документацию API CreateProcess , вы заметите, что он ожидает, что все аргументы командной строки будут объединены в большую строку (отсюда и вызов list2cmdline).

Кроме того, есть факт, что в UNIX-подобных системах на самом деле является оболочкой, которая может делать полезные вещи, поэтому я подозреваю, что другая причина заключается в том, что в Windows shell=True делает ничего, поэтому он работает так, как вы видите. Единственный способ заставить эти две системы работать одинаково - просто отбросить все аргументы командной строки, когда shell=True в Windows.

0 голосов
/ 10 декабря 2014

Причина поведения shell=True в UNIX заключается в цитировании. Когда мы пишем команду оболочки, она будет разделена пробелами, поэтому мы должны привести несколько аргументов:

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

...