Подтвердить что ты не робот

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

При использовании 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?

4b9b3361

Ответ 1

Фактически в 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]

Ответ 2

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

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

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

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

Вероятно, "почему" в UNIX-подобных системах аргументы команды фактически передаются приложениям (используя семейство вызовов exec*) в виде массива строк. Другими словами, вызывающий процесс решает, что входит в аргумент командной строки EACH. Если вы говорите ему использовать оболочку, вызывающий процесс на самом деле получает возможность передать только один аргумент командной строки для выполняемой оболочки: всю командную строку, которую вы хотите исполнить, исполняемое имя и аргументы, как одна строка.

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

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

Ответ 3

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

cp "My File" "New Location"

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

grep -r "\"hello\"" .

Иногда мы можем получить ужасные ситуации, в которых \ тоже нужно экранировать!

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

Popen(['cp', 'My File', 'New Location'])
Popen(['grep', '-r', '"hello"'])

Иногда бывает неплохо запускать "сырые" команды оболочки; например, если мы скопируем что-то из оболочки script или веб-сайта, и мы не хотим конвертировать все ужасное экранирование вручную. Поэтому существует опция shell=True:

Popen(['cp "My File" "New Location"'], shell=True)
Popen(['grep -r "\"hello\"" .'], shell=True)

Я не знаком с Windows, поэтому я не знаю, как и почему он ведет себя по-другому.