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