Сценарии, написанные для автоматизации определенных задач, часто должны выполнять или контролировать выполнение внешней программы, и Python позволяет создавать такие сценарии с помощью модуля subprocess. Этот модуль является частью стандартной библиотеки языка. В этом уроке мы рассмотрим подпроцесс и изучим его основы.
После прочтения статьи вы сможете узнать, как
Модуль subprocess в основном используется в Linux, поэтому все примеры относятся к Ubuntu; пользователям Windows рекомендуется загрузить терминал Ubuntu 18.04 LTS.
Функция «run»
Функция run был добавлен в модуль subprocess только в относительно недавней версии Python (3.5). В настоящее время это рекомендуемый способ создания процессов, который позволяет решать самые распространенные задачи. Сначала, в качестве простого случая, мы можем использовать run .
командование. ls -al ; Для этого в оболочке Python необходимо ввести следующие инструкции
копия копия копия копия используйте другой браузер
Запуск внешних приложений с помощью python
>>> import subprocess >>> process = subprocess.run([‘ls’, ‘-l’, ‘-a’])
На экране появится вывод внешней команды ls.
total 12 drwxr-xr-x 1 cnc cnc 4096 Apr 27 16:21 . drwxr-xr-x 1 root root 4096 Apr 27 15:40 .. -rw——- 1 cnc cnc 2445 May 6 17:43 .bash_history -rw-r—r— 1 cnc cnc 220 Apr 27 15:40 .bash_logout -rw-r—r— 1 cnc cnc 3771 Apr 27 15:40 .bashrc
Здесь мы просто используем первый необходимый аргумент run Это может быть либо последовательность, «описывающая» команду и ее аргументы, как в примере, либо shell=True (последний случай мы рассмотрим позже).
Захват вывода команды: stdout и stderr
Что если вы не хотите, чтобы вывод процесса отображался? Вместо этого, что если вы хотите сохранить его, чтобы можно было обратиться к нему после завершения процесса? В этом случае вы можете установить capture_output на True :
Копировать Копировать Использовать другой браузер
>>> process = subprocess.run([‘ls’, ‘-l’, ‘-a’], capture_output=True)
Что мы должны сделать с выходом после этого ( stdout и stderr ) процесса? Если вы посмотрите на пример выше, вы увидите, что мы используем process объект для ссылки на CompletedProcess возвращается run . Этот объект представляет процесс, выполняющий функцию, и обладает рядом полезных свойств. Он также обладает рядом других свойств. stdout и stderr которые, как уже упоминалось, используются для «хранения» соответствующих дескрипторов команд. capture_output устанавливается на True . В этом случае, чтобы захватить stdout для того, чтобы извлечь его.
>>> process = subprocess.run([‘ls’, ‘-l’, ‘-a’], capture_output=True) >>> process.stdout b’total 12ndrwxr-xr-x 1 cnc cnc 4096 Apr 27 16:21 .ndrwxr-xr-x 1 root root 4096 Apr 27 15:40 ..n-rw——- 1 cnc cnc 2445 May 6 17:43 .bash_historyn-rw-r—r— 1 cnc cnc 220 Apr 27 15:40 .bash_logout.
По умолчанию stdout и stderr являются байтовыми строками. Если вы хотите хранить их в виде строк, используйте функцию text функция run аргумент к True .
Уроки Python / Запуск внешних приложений
Управление сбоями процесса
Команда, выполненная в предыдущем примере, была выполнена без ошибок. Однако все случаи должны быть учтены при написании программы. Что же произойдет, если порожденный процесс потерпит неудачу? По умолчанию ничего особенного не происходит. Давайте рассмотрим пример ls пытается перечислить содержимое каталога /root, который недоступен для чтения обычным пользователям.
Копировать Копировать Использовать другой браузер
>>> process = subprocess.run([‘ls’, ‘-l’, ‘-a’, ‘/root’])
Вы можете определить, что запущенный процесс потерпел неудачу, проверив его код возврата. returncode свойство CompletedProcess :
Копировать Копировать Использовать другой браузер
>>> process.returncode 2
Вуаля. В этом случае returncode то же самое, что 2 что подтверждает, что процесс имел ошибку недостаточных разрешений и не завершился успешно. Вы можете проверить выход процесса и убедиться, что в случае неудачи будет вызвано исключение. Использование дискуссии check функция run если он установлен на True имеет значение True, то в случае сбоя внешнего процесса будет вызвано исключение. CalledProcessError :
>>> process = subprocess.run([‘ls’, ‘-l’, ‘-a’, ‘/root’]) ls: cannot open directory ‘/root’: Permission denied
Обработка исключений в Python очень проста. Итак, для обработки сбоев процесса вы можете написать что-то вроде этого
>>> try: . process = subprocess.run([‘ls’, ‘-l’, ‘-a’, ‘/root’], check=True) . except subprocess.CalledProcessError as e: . print(f»Ошибка команды !») . ls: cannot open directory ‘/root’: Permission denied [‘ls’, ‘-l’, ‘-a’, ‘/root’] failed! >>>
Исключения CalledProcessError выбрасывается, потому что, как уже упоминалось, код возврата процесса является . Этот объект имеет следующие свойства returncode , cmd , stdout , stderr Очевидно, что он представляет собой. Например, в приведенном выше примере мы просто используем свойство cmd для представления последовательности команд, которые должны быть выполнены при возникновении исключения.
Выполнение процесса в оболочке
Чтобы запустить процесс, используйте run Это означает, что при запуске не используется оболочка, поэтому процессу не доступны переменные окружения, и не происходит расширения или подстановки выражений. Давайте рассмотрим пример использования переменных $HOME :
>>> process = subprocess.run([‘ls’, ‘-al’, ‘$HOME’]) ls: cannot access ‘$HOME’: No such file or directory
Как вы можете видеть, это $HOME не был заменен на соответствующее значение. Такой способ запуска процесса рекомендуется во избежание потенциальных рисков безопасности. Однако в некоторых случаях, когда вам нужно запустить оболочку в качестве промежуточного процесса, достаточно настроить ее следующим образом shell устанавливается на True . В таких случаях рекомендуется указывать команду и ее аргументы в виде строк, например.
>>> process = subprocess.run(‘ls -al $HOME’, shell=True) total 12 drwxr-xr-x 1 cnc cnc 4096 Apr 27 16:21 . drwxr-xr-x 1 root root 4096 Apr 27 15:40 .. -rw——- 1 cnc cnc 2445 May 6 17:43 .bash_history -rw-r—r— 1 cnc cnc 220 Apr 27 15:40 .bash_logout .
Все переменные, существующие в пользовательском окружении, могут быть использованы при вызове оболочки в качестве промежуточного процесса. Это может показаться удобным, но может стать и источником проблем. Особенно при работе с потенциально опасными входными данными, которые могут привести к внедрению вредоносного шеллкода. Поэтому рекомендуется запускать процесс с помощью функции shell=True не рекомендуется и должно использоваться только в безопасных обстоятельствах.
Ограничение времени работы процесса
Обычно нежелательно, чтобы незаконно запущенный процесс работал в системе неопределенное время. Использование параметра timeout из run параметр позволяет указать количество времени, в секундах, которое отводится на завершение процесса. Если он не завершится в течение этого времени, сигнал SIGKILL остановит процесс. Это, как вы знаете, невозможно перехватить. Давайте продемонстрируем это, запустив длинный процесс. timeout в секундах.
>>> process = subprocess.run([‘ping’, ‘google.com’], timeout=5) PING google.com (216.58.208.206) 56(84) bytes of data. 64 bytes from par10s21-in-f206.1e100.net (216.58.208.206): icmp_seq=1 ttl=118 time=15.8 ms 64 bytes from par10s21-in-f206.1e100.net (216.58.208.206): icmp_seq=2 ttl=118 time=15.7 ms 64 bytes from par10s21-in-f206.1e100.net (216.58.208.206): icmp_seq=3 ttl=118 time=19.3 ms 64 bytes from par10s21-in-f206.1e100.net (216.58.208.206): icmp_seq=4 ttl=118 time=15.6 ms 64 bytes from par10s21-in-f206.1e100.net (216.58.208.206): icmp_seq=5 ttl=118 time=17.0 ms Traceback (most recent call last): File «», line 1, in File «/usr/lib/python3.8/subprocess.py», line 495, in run stdout, stderr = process.communicate(input, timeout=timeout) File «/usr/lib/python3.8/subprocess.py», line 1028, in communicate stdout, stderr = self._communicate(input, endtime, timeout) File «/usr/lib/python3.8/subprocess.py», line 1894, in _communicate self.wait(timeout=self._remaining_time(endtime)) File «/usr/lib/python3.8/subprocess.py», line 1083, in wait return self._wait(timeout=timeout) File «/usr/lib/python3.8/subprocess.py», line 1798, in _wait raise TimeoutExpired(self.args, timeout) subprocess.TimeoutExpired: Command ‘[‘ping’, ‘google.com’]’ timed out after 4.999637200000052 seconds
В приведенном выше примере мы выполняем команду ping без указания фиксированного количества пакетов ECHO REQUEST, поэтому это может занять вечность. Также для того, чтобы установить тайм-аут в 5 секунд timeout . Как вы можете видеть, пинг запускается, и через 5 секунд возникает исключение. TimeoutExpired отображается на дисплее, и процесс останавливается.
Функции call, check_output и check_call
Как упоминалось ранее, функция run является рекомендуемым способом запуска внешнего процесса. До его появления в Python 3.5 тремя основными функциями API высокого уровня, используемыми для создания процесса, были call , check_output и check_call Давайте посмотрим.
Во-первых, функция call Используется для выполнения команды, указанной параметром args, ожидая завершения команды, результатом чего является соответствующий код возврата. Это примерно соответствует базовому использованию функции run.
Как работает функция check_call по сути, то же самое, что и run где параметрами являются check устанавливается на True : вызвать заданную команду и дождаться ее завершения. Если код возврата возникнет исключение. CalledProcessError .
Наконец, функция check_output . . работает аналогично check_call но возвращает вывод запущенной программы, т.е. не отображается при выполнении функции.
Работа на более низком уровне с классом Popen
До сих пор мы обсуждали высокоуровневые API-функции модулей подпроцессов, в частности, модули run . Для всех чепчиков мы используем класс Popen . Поэтому в большинстве случаев нам не нужно манипулировать ими напрямую. Однако если вам нужна большая гибкость, вы можете обойтись без Popen требуется объект.
Например, предположим, что мы хотим соединить два процесса, воспроизведя поведение трубы shell: мы знаем, что если мы передаем две команды shell, то выход по умолчанию команды с левой стороны трубы «|» будет использоваться как вход по умолчанию для команды с правой стороны. В следующем примере результаты двух конвейерных команд хранятся в переменной.
$ output=»$(dmesg | grep sda)»
Чтобы воспроизвести это поведение в модуле подпроцесса без shell ценность. True как мы видели ранее. Popen :
Копировать Копировать Использовать другой браузер
dmesg = subprocess.Popen([‘dmesg’], stdout=subprocess.PIPE) grep = subprocess.Popen([‘grep’, ‘sda’], stdin=dmesg.stdout) dmesg.stdout.close () output = grep.comunicate()[0].
Рассматривая этот пример, важно помнить, что процесс Popen не будет блокировать выполнение сценария.
Первое, что мы сделали в приведенном выше фрагменте кода, — это добавили файл Popen объект, представляющий процесс dmesg . Мы установили stdout этого процесса, чтобы subprocess.PIPE . Это значение указывает на то, что трубопровод к указанной резьбе открыт.
Он создает еще один экземпляр Popen процесс. grep . В конструкторе Popen Конечно, мы даем команду и ее аргументы, но главное здесь то, что мы устанавливаем стандартный вывод процесса dmesg будет использоваться в качестве стандартного ввода для grep ( stdin=dmesg.stdout ) для воспроизведения поведения конвейера оболочки.
После создания Popen для этой цели. grep теперь закрыта. stdout процесс dmesg используется. close() . Это необходимо для того, чтобы первый процесс получил сигнал SIGPIPE, как описано в документации. Дело в том, что обычно, когда два процесса соединены конвейером, если один из них находится по правую сторону от «|» (т.е. grep выйдет раньше другого. dmesg ), последний получит сигнал SIGPIPE (трубопровод закрыт) и также завершится по умолчанию.
Однако если вы продублируете конвейер между двумя командами Python, вы столкнетесь с проблемами. stdout откроет родительский скрипт первого процесса, а также стандартный ввод другого процесса. Таким образом, даже если процесс grep завершается, stdout все еще будет открыт для вызывающего процесса (нашего скрипта), поэтому dmesg не получит сигнал SIGPIPE. Для этого закройте stdout первого процесса в главном сценарии после запуска второго процесса.
Последнее, что мы делаем, это звоним communicate() объект grep . Мы используем этот метод, чтобы по желанию передать данные в stdin процесс, и возвращает кортеж. Здесь первым элементом является stdout (на которую ссылается переменная output ), второй — stderr процесс.
Заключение
В этом учебнике рекомендуемый способ создания внешнего процесса в Python — это использование модуля subprocess и модуля run . В большинстве случаев этого будет достаточно. Однако, если требуется более высокий уровень гибкости, следует использовать непосредственно класс Popen.
Как всегда, мы рекомендуем вам ознакомиться с документацию subprocess для получения дополнительной информации о функциях и классах, доступных в этом модуле.
Источник: opythone.ru