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

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

Вступление

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

К счастью, мы можем использовать 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?

comments powered by Disqus