Рубрики
Без рубрики

Выполнение команд оболочки с помощью Python

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

Автор оригинала: Sajjad Heydari.

Выполнение команд оболочки с помощью Python

Вступление

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

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

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

Использование os.system для выполнения команды

Python позволяет нам немедленно выполнить команду оболочки, которая хранится в строке, используя функцию os.system () .

Давайте начнем с создания нового файла Python под названием echo_adelle.py и введите следующее:

import os

os.system("echo Hello from the other side!")

Первое, что мы делаем в нашем файле Python, – это импортируем модуль os , который содержит функцию system , которая может выполнять команды оболочки. Следующая строка делает именно это, запускает команду echo в нашей оболочке через Python.

В вашем терминале запустите этот файл с помощью следующей команды, и вы увидите соответствующий вывод:

$ python3 echo_adelle.py
Hello from the other side!

Поскольку команды echo выводятся на наш stdout , os.system() также выводит вывод на наш stdout поток. Хотя команда os.system() не отображается в консоли, она возвращает код выхода команды оболочки. Код выхода 0 означает, что он работал без каких-либо проблем, а любое другое число означает ошибку.

Давайте создадим новый файл с именем cd_return_codes.py и введите следующее:

import os

home_dir = os.system("cd ~")
print("`cd ~` ran with exit code %d" % home_dir)
unknown_dir = os.system("cd doesnotexist")
print("`cd doesnotexis` ran with exit code %d" % unknown_dir)

В этом скрипте мы создаем две переменные, которые хранят результат выполнения команд, изменяющих каталог на домашнюю папку, и на папку, которая не существует. Запустив этот файл, мы увидим:

$ python3 cd_return_codes.py
`cd ~` ran with exit code 0
sh: line 0: cd: doesnotexist: No such file or directory
`cd doesnotexist` ran with exit code 256

Первая команда, которая изменяет каталог на домашний, выполняется успешно. Поэтому os.system() возвращает свой код выхода ноль, который хранится в home_dir . С другой стороны, unknown_dir хранит код выхода неудачной команды bash для изменения каталога на несуществующую папку.

Функция os.system() выполняет команду, выводит любой вывод команды на консоль и возвращает код выхода команды. Если мы хотим более тонкого управления вводом и выводом команды оболочки в Python, мы должны использовать модуль subprocess .

Выполнение команды с подпроцессом

Модуль subprocess – это рекомендуемый Python способ выполнения команд оболочки. Это дает нам гибкость подавлять вывод команд оболочки или цепочку входов и выходов различных команд вместе, в то же время обеспечивая аналогичный опыт os.system() для основных случаев использования.

В новом файле под названием list_subprocess.py , напишите следующий код:

import subprocess

list_files = subprocess.run(["ls", "-l"])
print("The exit code was: %d" % list_files.returncode)

В первой строке мы импортируем модуль subprocess , который является частью стандартной библиотеки Python. Затем мы используем функцию subprocess.run() для выполнения команды. Как и os.system() , команда subprocess.run() возвращает код выхода того, что было выполнено.

В отличие от os.system() , обратите внимание, что subprocess.run() требует список строк в качестве входных данных вместо одной строки. Первый элемент списка – это имя команды. Остальные элементы списка-это флаги и аргументы команды.

Примечание: Как правило, вам нужно разделить аргументы на основе пробела, например ls-alh будет ["ls", "-alh"] , в то время как ls-a -l -h будет ["ls", "-a", - "l", "-h"] . В качестве другого примера echo hello world будет ["echo", "hello", "world"] , тогда как echo "hello world" или echo hello\ world будет ["echo", "hello world"] .

Запустите этот файл, и вывод вашей консоли будет похож на:

$ python3 list_subprocess.py
total 80
[email protected] 1 stackabuse  staff    216 Dec  6 10:29 cd_return_codes.py
[email protected] 1 stackabuse  staff     56 Dec  6 10:11 echo_adelle.py
[email protected] 1 stackabuse  staff    116 Dec  6 11:20 list_subprocess.py
The exit code was: 0

Теперь давайте попробуем использовать одну из более продвинутых функций subprocess.run() , а именно игнорировать вывод в stdout . В том же list_subprocess.py файл, изменение:

list_files = subprocess.run(["ls", "-l"])

К этому:

list_files = subprocess.run(["ls", "-l"], stdout=subprocess.DEVNULL)

Стандартный вывод команды теперь передается на специальное устройство /dev/null , что означает, что вывод не будет отображаться на наших консолях. Выполните файл в вашей оболочке, чтобы увидеть следующие выходные данные:

$ python3 list_subprocess.py
The exit code was: 0

Что делать, если мы хотим обеспечить ввод команды? subprocess.run() облегчает это своим аргументом input . Создайте новый файл с именем cat_subprocess.py , набрав следующее:

import subprocess

useless_cat_call = subprocess.run(["cat"], stdout=subprocess.PIPE, text=True, input="Hello from the other side")
print(useless_cat_call.stdout)  # Hello from the other side

Мы используем subprocess.run() с довольно большим количеством команд, давайте пройдемся по ним:

  • stdout=подпроцесс.PIPE сообщает Python перенаправить вывод команды на объект, чтобы позже его можно было прочитать вручную
  • text=True возвращает stdout и stderr в виде строк. Тип возвращаемого значения по умолчанию-байты.
  • input="Hello from the other side" говорит Python добавить строку в качестве входных данных в команду cat .

Запуск этого файла приводит к следующим выводам:

Hello from the other side

Мы также можем вызвать Исключение без ручной проверки возвращаемого значения. В новом файле, false_subprocess.py , добавьте код ниже:

import subprocess

failed_command = subprocess.run(["false"], check=True)
print("The exit code was: %d" % failed_command.returncode)

В вашем терминале запустите этот файл. Вы увидите следующую ошибку:

$ python3 false_subprocess.py
Traceback (most recent call last):
  File "false_subprocess.py", line 4, in 
    failed_command = subprocess.run(["false"], check=True)
  File "/usr/local/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 512, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1.

Используя check=True , мы говорим Python вызывать любые исключения, если возникает ошибка. Поскольку мы действительно столкнулись с ошибкой, оператор print в последней строке не был выполнен.

Функция subprocess.run() дает нам огромную гибкость, которой os.system() не обладает при выполнении команд оболочки. Эта функция является упрощенной абстракцией подпроцесса .Popen класс, который предоставляет дополнительную функциональность, которую мы можем исследовать.

Запуск команды с помощью Popen

Подпроцесс .Класс Popen предоставляет разработчику больше возможностей при взаимодействии с оболочкой. Однако нам нужно быть более откровенными в отношении получения результатов и ошибок.

По умолчанию используется подпроцесс.Popen не останавливает обработку программы Python, если ее команда не закончила выполнение. В новом файле под названием list_popen.py , введите следующее:

import subprocess

list_dir = subprocess.Popen(["ls", "-l"])
list_dir.wait()

Этот код эквивалентен коду list_subprocess.py . Он запускает команду с помощью подпроцесса .Popen и ждет его завершения , прежде чем выполнить остальную часть скрипта Python.

Допустим, мы не хотим ждать, пока наша команда оболочки завершит выполнение, чтобы программа могла работать над другими вещами. Как он узнает, когда команда оболочки завершит выполнение?

Метод poll() возвращает код выхода, если команда закончила выполнение, или None , если она все еще выполняется. Например, если бы мы хотели проверить, был ли list_dir завершен, а не ждать его, у нас была бы следующая строка кода:

list_dir.poll()

Для управления вводом и выводом с помощью подпроцесса .Popen , нам нужно использовать метод communicate () .

В новом файле под названием cat_popen.py , добавьте следующий фрагмент кода:

import subprocess

useless_cat_call = subprocess.Popen(["cat"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, errors = useless_cat_call.communicate(input="Hello from the other side!")
useless_cat_call.wait()
print(output)
print(errors)

Метод communicate() принимает аргумент input , который используется для передачи входных данных команде оболочки. Метод communicate также возвращает оба параметра stdout и stderr , когда они установлены.

Увидев основные идеи, лежащие в основе подпроцесса.Popen , теперь мы рассмотрели три способа запуска команд оболочки в Python. Давайте еще раз рассмотрим их характеристики, чтобы узнать, какой метод лучше всего подходит для требований проекта.

Какой из них я должен использовать?

Если вам нужно выполнить одну или несколько простых команд и вы не возражаете, если их вывод идет на консоль, вы можете использовать команду os.system () . Если вы хотите управлять вводом и выводом команды оболочки, используйте subprocess.run() . Если вы хотите запустить команду и продолжать выполнять другую работу во время ее выполнения, используйте subprocess.Попен .

Вот таблица с некоторыми различиями в удобстве использования, которые вы также можете использовать для обоснования своего решения:

нет Требует разбора аргументов да да
да Ждет команды да нет
нет Связывается с stdin и stdout да да
возвращаемое значение Возвращается объект объект

Вывод

Python позволяет вам выполнять команды оболочки, которые вы можете использовать для запуска других программ или лучше управлять сценариями оболочки, которые вы используете для автоматизации. В зависимости от нашего варианта использования мы можем использовать os.system () , subprocess.run() или subprocess.Popen для выполнения команд bash.

Используя эти методы, какую внешнюю задачу вы бы запустили через Python?