subprocess в Python

Содержание
Введение
Простой пример Linux
Простой пример Windows
Обработка ошибок
Bash команда с опциями
Передать переменную в аргумент команды
args, returncode, stdout
Передача аргументов в скрипт
Логи с помощью subprocess
Сравнить два файла
Определить версию Linux
Похожие статьи

Введение

В этой статье вы узнаете как выполнять команды Linux и Windows из кода на Python 3.

Создайте файл subprocess_lesson.py и копируйте туда код из примеров.

Запустить скрипт можно командой

python3 subprocess.py

Простой пример

Пример программы, которая выполняет Linux команду ls

import subprocess subprocess.run('ls')

Простой пример Windows

Пример программы, которая выполняет в Windows команду dir

import subprocess subprocess.run('dir', shell=True)

У меня пока что не работает

Bash команда с опциями

Чтобы выполнить Bash команду с опциями, например, ls - la нужно добавить shell=True

import subprocess subprocess.run('ls -la', shell=True)

У использования shell=True есть одна важная особенность: нужно особенно внимательно следить за безопастностью.

Рекомендуется использовать shell=True только если вы передаёте параметры самостоятельно.

Избежать использования shell=True можно передав команду и параметры списком:

import subprocess subprocess.run(['ls', '-la'])

Передать переменную в аргумент команды

По аналогии с предыдущим параграфом - в качестве аргумента можно использовать и переменную

import subprocess text = "Visit TopBicycle.ru to support my website" subprocess.run(["echo",text])

python3 subprocess_lesson.py

Visit TopBicycle.ru to support my website

args, returncode, stdout

Разберём subprocess более подробно

import subprocess p1 = subprocess.run(['ls', '-la']) print("p1") print(p1) print("p1.args") print(p1.args) print("p1.returncode") print(p1.returncode) print("p1.stdout") print(p1.stdout)

python3 subprocess_lesson.py

total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 17:57 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 17:57 .. -rw-rw-r-- 1 andrei andrei 195 Nov 30 16:51 subprocess_lesson.py p1 CompletedProcess(args='ls -la', returncode=0) p1.args ls -la p1.returncode 0 p1.stdout None

Чтобы не выводить результат в терминал а сохранить в переменную, нужно воспользоваться опцией capture_output=True - доступна, начиная с версии Python 3.7

import subprocess p1 = subprocess.run(['ls', '-la'], capture_output=True) print(p1.stdout)

python3 subprocess_lesson.py

b'total 12\ndrwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 .\ndrwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 ..\n-rw-rw-r-- 1 andrei andrei 92 Nov 30 18:41 subprocess_lesson.py\n'

Если byte вывод вам не нравится его можно декодировать

import subprocess p1 = subprocess.run(['ls', '-la'], capture_output=True) print(p1.stdout.decode())

python3 subprocess_lesson.py

total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r-- 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py

Или можно использовать опцию text=True

import subprocess p1 = subprocess.run(['ls', '-la'], capture_output=True, text=True) print(p1.stdout)

total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r-- 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py

Ещё один вариант перенаправления вывода stdout=subprocess.PIPE

import subprocess p1 = subprocess.run(['ls', '-la'], stdout=subprocess.PIPE, text=True) print(p1.stdout)

python3 subprocess_lesson.py

total 12 drwxrwxr-x 2 andrei andrei 4096 Nov 30 18:41 . drwxrwxr-x 3 andrei andrei 4096 Nov 30 18:40 .. -rw-rw-r-- 1 andrei andrei 101 Nov 30 18:46 subprocess_lesson.py

Вывод в файл

import subprocess with open('output.txt', 'w') as f: p1 = subprocess.run(['ls', '-la'], stdout=f, text=True)

Обработка ошибок

Добавим заведомо неверное условие в команду. Например, пусть листинг выполняется не для текущей директории а для несуществующей.

import subprocess p1 = subprocess.run(['ls', '-la', 'not_exist'], capture_output=True, text=True) print(p1.returncode) print(p1.stderr)

2 ls: cannot access 'not_exist': No such file or directory

Обратите внимане, что Python в этом примере не выдаёт никаких ошибок

Чтобы Python информировал об ошибках во внешних командах используйте опцию check=True

import subprocess p1 = subprocess.run(['ls', '-la', 'not_exist'], capture_output=True, text=True, check=True) print(p1.returncode) print(p1.stderr)

python3 subprocess_lesson.py

Traceback (most recent call last): File "subprocess_lesson.py", line 3, in <module> p1 = subprocess.run(['ls', '-la', 'not_exist'], capture_output=True, text=True, check=True) File "/usr/lib/python3.8/subprocess.py", line 512, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command '['ls', '-la', 'not_exist']' returned non-zero exit status 2.

Обратите внимане, что теперь Python выдаёт ошибку, а до print(p1.returncode) и print(p1.stderr) дело уже не доходит

import subprocess p1 = subprocess.run(['ls', '-la', 'not_exist'], stderr=subprocess.DEVNULL) print(p1.stderr)

python3 subprocess_lesson.py

None

Передача аргументов в скрипт

Допустим, нужно вызвать скрипт с несколькими аргументами

import subprocess subprocess.call(['./script.sh %s %s %s' %(ip, username, test_time)], shell=True)

Ещё пример: из python скрипта вызвать sed и обрезать число строк, которое сам скрипт получает как аргумент

import subprocess LINES = int(sys.argv[1]) subprocess.call(['sed -i -e 1,%sd 2024-03-28-log.txt' %(LINES)], shell=True)

Эту задачу можно решить на чистом Python решение

with open('file_with_lines.txt', 'r') as fin: data = fin.readlines()[3:] with open('file_with_lines.txt', 'w') as fout: fout.writelines(data)

Логи с помощью subprocess

Если запускать код в какой-то среде, где лог в файл неудобен а лог с помощью print невозможен, можно использовать echo из bash

import subprocess text = "Andrei Log: robot/src/libraries/TestController.py is running" subprocess.run(["echo", text])

Сравнить два файла

Если запускать код в какой-то среде, где лог в файл неудобен а лог с помощью print невозможен, можно использовать echo из bash

import subprocess def compare(file1, file2): subprocess.run(["diff", file1, file2])

Определить версию Linux

С помощью subprocess можно в том числе определить версию Linux

В примере ниже определяются CentOS, RedHat, Rocky, Ubuntu

import subprocess import sys CENTOS = {"os_name": "CentOS", "cmd": "rpm --eval %{centos_ver}"} REDHAT = {"os_name": "Red", "cmd": "rpm --eval %{red_hat_ver}"} ROCKY = {"os_name": "Rocky", "cmd": "rpm --eval %{rocky_ver}"} UBUNTU = {"os_name": "Ubuntu", "cmd": "cat /etc/issue"} OS_LIST = [CENTOS, REDHAT, ROCKY, UBUNTU] def find_os() -> object: try: p = subprocess.run(["lsb_release", "-a"], capture_output=True, text=True) except Exception as e: print(f"lsb_release -a call failed: {e!r}", file=sys.stderr) raise system_release = str(p.stdout) + str(p.stderr) system_release = system_release.split() for os in OS_LIST: name = os["os_name"] if name in system_release: break else: os = None return os def get_name(os) -> str: name = os["os_name"] return name def get_version(os) -> str: cmd = os["cmd"] cmd = cmd.split() p = subprocess.run(cmd, capture_output=True, text=True) version = str(p.stdout) try: version = int(version) except: version = version.split() version = version[1] return version def get_linux_version() -> tuple: os = find_os() if os is not None: name = get_name(os) version = get_version(os) linux_version = (name, version) else: print("os is not found") linux_version = (None, None) return linux_version if __name__ == '__main__': print(get_linux_version())