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

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

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

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

а также

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

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

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

7 ответов

Решение

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

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

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

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

В итоге, используйте shell=False,

>>> 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 рассматривается как простая команда.

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

Пример, где все может пойти не так с 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 ()

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

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

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

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

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

В тривиальном случае просто замените

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.

Анусер выше объясняет это правильно, но недостаточно прямо. Позвольте использовать ps команду, чтобы увидеть, что происходит.

      import time
import subprocess

s = subprocess.Popen(["sleep 100"], shell=True)
print("start")
print(s.pid)
time.sleep(5)
s.kill()
print("finish")

Запустите его и покажет

      start
832758
finish

Затем вы можете использовать ps -auxf > 1 раньше, а затем ps -auxf > 2 после finish. Вот результат

1

      cy         71209  0.0  0.0   9184  4580 pts/6    Ss   Oct20   0:00  |       \_ /bin/bash
cy        832757  0.2  0.0  13324  9600 pts/6    S+   19:31   0:00  |       |   \_ python /home/cy/Desktop/test.py
cy        832758  0.0  0.0   2616   612 pts/6    S+   19:31   0:00  |       |       \_ /bin/sh -c sleep 100
cy        832759  0.0  0.0   5448   532 pts/6    S+   19:31   0:00  |       |           \_ sleep 100

Видеть? Вместо прямого запуска sleep 100. он действительно работает. и то, что он распечатывает, на самом деле pidиз . После, если вы позвоните s.kill(), это убивает, но sleep все еще там.

2

      cy         69369  0.0  0.0 533764  8160 ?        Ssl  Oct20   0:12  \_ /usr/libexec/xdg-desktop-portal
cy         69411  0.0  0.0 491652 14856 ?        Ssl  Oct20   0:04  \_ /usr/libexec/xdg-desktop-portal-gtk
cy        832646  0.0  0.0   5448   596 pts/6    S    19:30   0:00  \_ sleep 100

Итак, следующий вопрос: что может /bin/shделать? Каждый пользователь Linux знает, слышал и использует. Но держу пари, что есть так много людей, которые действительно не понимают, что такое shellдействительно. Может ты тоже слышишь /bin/bash, они похожи.

Одна из очевидных функций оболочки - для удобства пользователей запускать приложение Linux. из-за программы оболочки, такой как sh или bash, вы можете напрямую использовать команду вроде, а не /usr/bin/ls. он будет искать где ls есть и запускает его для вас.

Другая функция - интерпретировать строку после $как переменная среды. Вы можете сравнить эти два скрипта на Python, чтобы убедиться в этом сами.

      subprocess.call(["echo $PATH"], shell=True)
      subprocess.call(["echo", "$PATH"])

И самое главное, это позволяет запускать команду linux как скрипт. Такие как if elseвводятся оболочкой. это не родная команда Linux

предположим, что вы используете shell=False и предоставляете команду в виде списка. И какой-то злоумышленник попытался ввести команду rm. Вы увидите, что 'rm' будет интерпретироваться как аргумент, и фактически 'ls' попытается найти файл с именем 'rm'.

      >>> subprocess.run(['ls','-ld','/home','rm','/etc/passwd'])
ls: rm: No such file or directory
-rw-r--r--    1 root     root          1172 May 28  2020 /etc/passwd
drwxr-xr-x    2 root     root          4096 May 29  2020 /home
CompletedProcess(args=['ls', '-ld', '/home', 'rm', '/etc/passwd'], returncode=1)

shell=False не является безопасным по умолчанию, если вы не контролируете ввод должным образом. Вы по-прежнему можете выполнять опасные команды.

      >>> subprocess.run(['rm','-rf','/home'])
CompletedProcess(args=['rm', '-rf', '/home'], returncode=0)
>>> subprocess.run(['ls','-ld','/home'])
ls: /home: No such file or directory
CompletedProcess(args=['ls', '-ld', '/home'], returncode=1)
>>>

Я пишу большинство своих приложений в контейнерных средах, я знаю, какая оболочка вызывается, и я не беру никаких пользовательских данных.

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

Другие вопросы по тегам