Фактическое значение 'shell = True' в подпроцессе

260

Я называю разные процессы с subprocessмодулем. Однако у меня есть вопрос.

В следующих кодах:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

и

callProcess = subprocess.Popen(['ls', '-l']) # without shell

Оба работают. Прочитав документы, я узнал, что это shell=Trueозначает выполнение кода через оболочку. Таким образом, это означает, что в отсутствие процесс запускается напрямую.

Итак, что я предпочитаю для своего случая - мне нужно запустить процесс и получить его вывод. Какую выгоду я получаю от вызова изнутри или снаружи.

user225312
источник
21
первая команда неверна: -lпередается /bin/sh(оболочке) вместо lsпрограммы в Unix, еслиshell=True . Строковый аргумент должен использоваться shell=Trueв большинстве случаев вместо списка.
Jfs
1
re "процесс непосредственно запущен": Wut?
allyourcode
9
Утверждение «Обе работы». о тех 2 звонках неверно и вводит в заблуждение. Звонки работают по-другому. Простое переключение с shell=Trueна Falseи наоборот является ошибкой. Из документации : «В POSIX с shell = True, (...) Если args является последовательностью, первый элемент задает командную строку, а любые дополнительные элементы будут рассматриваться как дополнительные аргументы для самой оболочки.». В Windows есть автоматическое преобразование , которое может быть нежелательным.
mbdevpl
См. Также stackoverflow.com/q/59641747/874188
tripleee

Ответы:

183

Преимущество отказа от вызова через оболочку состоит в том, что вы не вызываете «загадочную программу». В POSIX переменная среды SHELLопределяет, какой двоичный файл вызывается как «оболочка». В Windows нет потомка оболочки Bourne, только cmd.exe.

Таким образом, вызов оболочки вызывает программу по выбору пользователя и зависит от платформы. Вообще говоря, избегайте вызовов через оболочку.

Вызов через оболочку позволяет расширять переменные среды и файловые глобусы в соответствии с обычным механизмом оболочки. В системах POSIX оболочка расширяет файловые глобусы до списка файлов. В операционной системе Windows, файл - Глоб (например, «*. *») Не расширяется за счет оболочки, так или иначе (но переменные окружения в командной строке будут расширены cmd.exe).

Если вы считаете, что вам нужны расширения переменных среды и глобусы файлов, изучите ILSатаки 1992-го года на сетевые службы, которые выполняли вызовы подпрограмм через оболочку. Примеры включают в себя различные sendmailбэкдоры с участием ILS.

В заключение, используйте shell=False.

Хит Ханникут
источник
2
Спасибо за ответ. Хотя я на самом деле не на той стадии, когда мне следует беспокоиться о подвигах, но я понимаю, к чему вы клоните.
user225312
55
Если в начале вы небрежны, никакое беспокойство не поможет вам наверстать упущенное позже. ;)
Хит Ханникутт
Что если вы хотите ограничить максимальную память подпроцесса? stackoverflow.com/questions/3172470/…
Pramod
8
утверждение о $SHELLневерно. Процитируем subprocess.html: «В Unix с shell=Trueоболочкой по умолчанию /bin/sh.» (не $SHELL)
Марцин
1
@ user2428107: Да, если вы используете вызов backtick на Perl, вы используете вызов оболочки и открываете те же проблемы. Используйте 3+ arg, openесли вы хотите использовать безопасные способы вызова программы и получения результатов.
ShadowRanger
137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

Установка аргумента оболочки в истинное значение заставляет подпроцесс порождать промежуточный процесс оболочки и сообщать ему о запуске команды. Другими словами, использование промежуточной оболочки означает, что переменные, шаблоны глобусов и другие специальные функции оболочки в командной строке обрабатываются до запуска команды. Здесь, в примере, $ HOME был обработан перед командой echo. На самом деле, это случай команды с расширением оболочки, тогда как команда ls -l рассматривается как простая команда.

источник: модуль подпроцесса

Мина Габриэль
источник
16
Не знаю, почему это не выбранный ответ. Безусловно, тот, который действительно соответствует вопросу
Родриго Лопес Герра
1
согласна. это хороший пример для меня, чтобы понять, что означает shell = True.
user389955
2
Установка истинного значения аргумента оболочки приводит к тому, что подпроцесс порождает промежуточный процесс оболочки и сообщает ему, что нужно выполнить команду « О боже, это все говорит». Почему этот ответ не принят ??? Зачем?
Pouya
Я думаю, что проблема заключается в том, что первым аргументом для вызова является список, а не строка, но это дает ошибку, если shell имеет значение False. Смена команды в списке сделает эту работу
Линкольн Рэндалл Макфарланд
Извините, мой предыдущий комментарий был сделан до того, как я закончил. Для ясности: я часто вижу использование подпроцесса с shell = True, и команда является строкой, например, 'ls -l', (я ожидаю избежать этой ошибки), но подпроцесс принимает список (и строку как список из одного элемента) , Для запуска без вызова оболочки (и проблем с безопасностью ) используйте список subprocess.call (['ls', '-l'])
Линкольн Рэндалл МакФарланд,
42

Пример, где все может пойти не так с Shell = True, показан здесь

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

Проверьте документ здесь: subprocess.call ()

Richeek
источник
6
Ссылка очень полезная. Как указывалось в ссылке: Выполнение команд оболочки, которые содержат неанизированный ввод из ненадежного источника, делает программу уязвимой для внедрения оболочки, серьезный недостаток безопасности, который может привести к выполнению произвольной команды. По этой причине использование shell = True настоятельно не рекомендуется в тех случаях, когда командная строка создается из внешнего ввода.
JTUKI
39

Выполнение программ через оболочку означает, что весь пользовательский ввод, передаваемый в программу, интерпретируется в соответствии с синтаксическими и семантическими правилами вызванной оболочки. В лучшем случае это только доставляет неудобства пользователю, потому что пользователь должен соблюдать эти правила. Например, пути, содержащие специальные символы оболочки, такие как кавычки или пробелы, должны быть экранированы. В худшем случае это приводит к утечкам безопасности, поскольку пользователь может выполнять произвольные программы.

shell=Trueиногда удобно использовать определенные функции оболочки, такие как разделение слов или расширение параметров. Однако, если такая функция требуется, используйте другие модули (например, os.path.expandvars()для расширения параметров или shlexдля разделения слов). Это означает больше работы, но позволяет избежать других проблем.

Короче говоря: избегайте во что shell=Trueбы то ни стало.

lunaryorn
источник
16

Другие ответы здесь адекватно объясняют предостережения безопасности, которые также упоминаются в subprocessдокументации. Но в дополнение к этому, издержки запуска оболочки для запуска программы, которую вы хотите запустить, часто не нужны и, безусловно, глупы в ситуациях, когда вы фактически не используете какую-либо функциональность оболочки. Более того, дополнительная скрытая сложность должна пугать вас, особенно если вы не очень хорошо знакомы с оболочкой или предоставляемыми ей службами.

Там, где взаимодействия с оболочкой нетривиальны, теперь вам требуется, чтобы читатель и сопровождающий сценария Python (который может быть вашим будущим я или не мог) понимал как Python, так и сценарий оболочки. Помните девиз Python «явное лучше, чем неявное»; даже если код Python будет несколько более сложным, чем эквивалентный (и часто очень краткий) сценарий оболочки, вам может быть лучше удалить оболочку и заменить функциональность собственными конструкциями Python. Минимизация работы, выполняемой во внешнем процессе, и сохранение контроля в собственном коде, насколько это возможно, часто является хорошей идеей просто потому, что она улучшает видимость и снижает риски нежелательных или нежелательных побочных эффектов.

Расширение подстановочных знаков, интерполяция переменных и перенаправление - все это легко заменить на собственные конструкции Python. Сложный конвейер оболочки, где части или все не могут быть разумно переписаны в Python, был бы единственной ситуацией, когда вы могли бы рассмотреть возможность использования оболочки. Вы все равно должны убедиться, что понимаете последствия для производительности и безопасности.

В тривиальном случае, чтобы избежать shell=True, просто замените

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

с участием

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

Обратите внимание, как первый аргумент представляет собой список строк для передачи execvp(), и как в кавычках строки и метасимволы оболочки с обратной косой чертой обычно не нужны (или полезны, или корректны). Может быть, смотрите также Когда обернуть кавычки вокруг переменной оболочки?

Кроме того, вы очень часто хотите избежать, Popenесли один из более простых упаковщиков в subprocessпакете делает то, что вы хотите. Если у вас достаточно недавний Python, вы, вероятно, должны использовать subprocess.run.

  • При check=Trueэтом произойдет сбой, если команда, которую вы запустили, не удалась.
  • С stdout=subprocess.PIPEего помощью будет получен вывод команды.
  • Несколько непонятно, с universal_newlines=Trueэтим он будет декодировать вывод в правильную строку Unicode ( bytesиначе это просто в системной кодировке на Python 3).

Если нет, то для многих задач вы хотите check_outputполучить выходные данные команды, одновременно проверяя, успешно check_callли она выполнена или нет ли выходных данных для сбора.

В заключение я приведу цитату Дэвида Корна: «Легче написать переносную оболочку, чем сценарий переносимой оболочки». Даже subprocess.run('echo "$HOME"', shell=True)не переносится на Windows.

tripleee
источник
Я думал, что цитата была от Ларри Уолла, но Google говорит мне иначе.
tripleee
Это громкий разговор, но никаких технических предложений по замене: здесь, на OS-X, я пытаюсь получить pid Mac-приложения, которое я запустил через «open»: process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- но это не сработает, если я не буду использовать shell = True. Что теперь?
Мотти Шнеор
Есть масса вопросов о том, как избежать shell=True, многие с отличными ответами. Вы случайно выбрали ту, которая о том, почему вместо этого.
tripleee
@MottiShneor Спасибо за отзыв; добавлен простой пример
tripleee
Возможно, смотрите также мой ответ на общий вопрос оsubprocess
tripleee