Автор оригинала: 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, infailed_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?