Фактическое значение '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)
>>>
Я пишу большинство своих приложений в контейнерных средах, я знаю, какая оболочка вызывается, и я не беру никаких пользовательских данных.
Поэтому в моем случае использования я не вижу угрозы безопасности. И гораздо проще создать длинную строку команд. Надеюсь, я не ошибаюсь.