subprocess в Python
Введение
В этой статье вы узнаете как выполнять команды
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 2023-10-04-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())