subprocess в Python
Введение
В этой статье вы узнаете как выполнять команды
Linux
и
Windows
из кода на Python 3.
Создайте файл
subprocess_demo.py
и копируйте туда код из примеров.
Запустить скрипт можно командой
python subprocess_demo.py
Простой пример в Linux
Это пример для Linux. Простой пример для Windows здесь
Пример программы, которая выполняет
Linux
команду ls
import subprocess subprocess.run('ls')
Простой пример в Windows
Это пример для Windows. Простой пример для Linux здесь
Пример программы, которая выполняет в
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_demo.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_demo.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_demo.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_demo.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_demo.py\n'
Если byte вывод вам не нравится его можно декодировать
import subprocess
p1 = subprocess.run(['ls', '-la'], capture_output=True)
print(p1.stdout.decode())
python3 subprocess_demo.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_demo.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_demo.py
Ещё один вариант перенаправления вывода stdout=subprocess.PIPE
import subprocess
p1 = subprocess.run(['ls', '-la'], stdout=subprocess.PIPE, text=True)
print(p1.stdout)
python3 subprocess_demo.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_demo.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_demo.py
Traceback (most recent call last): File "subprocess_demo.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_demo.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 2025-07-08-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())
subprocess.Popen()
sp_demo/ |-- app.py |-- demo.py `-- output.txt
# app.py _input = input("enter a string: ") _output = "hello " + _input with open("output.txt", "w") as f: f.write(_output)
# demo.py import sys import os.path import subprocess app_path = os.path.join(os.path.dirname(__file__), "app.py") command = [sys.executable, app_path] process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) # Передадим в input скрипта app.py строку # с помощью метода communicate() process.communicate(input="www.devhops.ru\n".encode())
python demo.py
cat output.txt
hello www.devhops.ru
.communicate()
# demo.py import sys import os.path import subprocess app_path = os.path.join(os.path.dirname(__file__), "app.py") command = [sys.executable, app_path] process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) out, err = process.communicate(input="www.devhops.ru\n".encode()) if process.returncode != 0: print(f"out: {out}") print(f"err: {err}")
python demo.py
cat output.txt
hello www.devhops.ru
Изучим как будет обрабатываться ошибка, например, обращение к несуществующему файлу
# … process = subprocess.Popen("python app1.py", # …
out: b'' err: b"C:\Users\A\AppData\Local\Programs\Python\Python312\python.exe: can't open file 'C:\\sp_demo\\app1.py': [Errno 2] No such file or directory\r\n"
Обратите внимание, что сейчас в стандартном выводе пусто, зато есть ошибка в stderr, которая остаётся в PIPE и потом с помощью communicate передаётся в err.
Оставим ошибку в имени app1.py и заменим stderr с PIPE на STDOUT
# demo.py import sys import os.path import subprocess app_path = os.path.join(os.path.dirname(__file__), "app1.py") command = [sys.executable, app_path] process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True ) out, err = process.communicate(input="www.devhops.ru\n".encode()) if process.returncode != 0: print(f"out: {out}") print(f"err: {err}")
out: b"C:\Users\A\AppData\Local\Programs\Python\Python312\python.exe: can't open file 'C:\\sp_demo\\app1.py': [Errno 2] No such file or directory\r\n" err: None
Обратите внимание на то, что в err, из communicate теперь ничего нет. Ошибка теперь отравляется в stdout и оттуда попадает из communicate в out
Запустить неблокирующий процесс
Если нужно из одного скрипта сначала запустить процесс, который занимает вывод, а потом, например, обращаться к его API поможет флаг close_fds
subprocess.Popen("C:\Server.exe", close_fds=True)
Автор статьи: Андрей Олегович