Вступление
Повторяющиеся задачи созрели для автоматизации. Разработчики и системные администраторы обычно автоматизируют рутинные задачи, такие как проверки работоспособности и резервное копирование файлов, с помощью сценариев оболочки. Однако по мере того, как эти задачи становятся более сложными, сценарии оболочки может становиться все труднее поддерживать.
К счастью, мы можем использовать Python вместо сценариев оболочки для автоматизации. 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
Запуск команды с подпроцессом
Модуль подпроцесса
- это рекомендуемый Python способ выполнения команд оболочки. Это дает
нам гибкость для подавления вывода команд оболочки или цепочки входов и
выходов различных команд вместе, при этом обеспечивая аналогичный опыт с
os.system()
для базовых случаев использования.
В новом list_subprocess.py
именем 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=subprocess.PIPE
указывает Python перенаправить вывод команды на объект, чтобы его можно было вручную прочитать позжеtext=True
возвращаетstdout
иstderr
как строки. Тип возвращаемого значения по умолчанию - байты.input="Hello from the other side"
указывает Python добавить строку в качестве входных данных для командыcat
.
Запуск этого файла дает следующий результат:
Hello from the other side
Мы также можем вызвать Exception
не проверяя возвращаемое значение
вручную. В новом файле 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 <module>
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()
при выполнении команд оболочки. Эта функция является
упрощенной абстракцией subprocess.Popen
, который предоставляет
дополнительные возможности, которые мы можем изучить.
Выполнение команды с помощью Popen
Класс subprocess.Popen
предоставляет разработчику больше возможностей
при взаимодействии с оболочкой. Однако нам нужно более подробно
описывать получение результатов и ошибок.
По умолчанию subprocess.Popen
не останавливает обработку программы
Python, если ее команда не завершила выполнение. В новом файле с именем
list_popen.py
введите следующее:
import subprocess
list_dir = subprocess.Popen(["ls", "-l"])
list_dir.wait()
Этот код эквивалентен list_subprocess.py
. Он запускает команду,
используя subprocess.Popen
, и ожидает ее завершения перед выполнением
остальной части скрипта Python.
Допустим, мы не хотим ждать, пока наша команда оболочки завершит выполнение, чтобы программа могла работать над другими вещами. Как он узнает, что команда оболочки завершила выполнение?
Метод poll()
возвращает код выхода, если команда завершена, или None
если она все еще выполняется. Например, если бы мы хотели проверить,
list_dir
а не ждать его, у нас была бы следующая строка кода:
list_dir.poll()
Чтобы управлять вводом и выводом с помощью subprocess.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
если они установлены.
Ознакомившись с основными идеями subprocess.Popen
, мы рассмотрели три
способа запуска команд оболочки в Python. Давайте еще раз рассмотрим их
характеристики, чтобы узнать, какой метод лучше всего подходит для
требований проекта.
Какой мне использовать?
Если вам нужно запустить одну или несколько простых команд и вы не
возражаете, если их вывод пойдет на консоль, вы можете использовать
команду os.system()
. Если вы хотите управлять вводом и выводом
команды оболочки, используйте subprocess.run()
. Если вы хотите
запустить команду и продолжить выполнение другой работы во время ее
выполнения, используйте subprocess.Popen
.
Вот таблица с некоторыми различиями в удобстве использования, которую вы также можете использовать для обоснования своего решения:
os.system
subprocess.run
subprocess.Popen
Требуются проанализированные аргументы
нет
да
да
Ждет команды
да
да
нет
Обменивается данными с stdin и stdout
нет
да
да
Возврат
возвращаемое значение
объект
объект
Заключение
Python позволяет выполнять команды оболочки, которые можно использовать
для запуска других программ или лучшего управления сценариями оболочки,
которые вы используете для автоматизации. В зависимости от нашего
os.system()
, subprocess.run()
или subprocess.Popen
для запуска
команд bash.
Используя эти методы, какую внешнюю задачу вы бы запустили через Python?