Запуск внешних программ и команд средствами Python

Раздел: Системное программирование -> Взаимодействие с ОС

При разработке системных утилит и автоматизации часто требуется вызывать внешние программы из скриптов Python. Библиотеки языка предоставляют несколько механизмов, каждый из которых подходит для конкретных сценариев: от простого запуска до полного управления потоком ввода-вывода и жизненным циклом процесса.

Основные методы вызова программ из Python

Как выполнить команду и дождаться её завершения, получив код возврата и вывод?

Наиболее современным и рекомендуемым решением является модуль subprocess с функцией run() (Python 3.5+). Она принимает список аргументов или строку (при указании shell=True), запускает процесс, ожидает его завершения и возвращает объект CompletedProcess с атрибутами stdout, stderr и returncode.

import subprocess
result = subprocess.run(['ls', '-la'], capture_output=True, text=True)
print('Код возврата:', result.returncode)
print('Stdout:', result.stdout)
print('Stderr:', result.stderr)

Python run exe file (запуск exe-файла из python)

Пояснение: capture_output=True перенаправляет stdout и stderr в атрибуты, text=True возвращает строки (вместо байтов). Без capture_output вывод уходит на терминал.

Типичные проблемы:

  • При shell=True повышается риск инъекций команд, если аргументы передаются из ненадёжных источников.
  • Если команда генерирует большой объём данных, передача через capture_output может привести к переполнению памяти. В таких случаях лучше читать поток постепенно через Popen.
  • Кодировка вывода по умолчанию зависит от платформы. Явно указывайте encoding='utf-8' для единообразия.
  • Если команда зависает, поможет аргумент timeout.

Цель использования: Подходит для большинства задач, где требуется выполнить внешнюю программу, дождаться результата и обработать вывод или код ошибки.

Как запустить процесс в фоне и взаимодействовать с ним через потоки stdin/stdout/stderr?

Класс subprocess.Popen предоставляет низкоуровневый интерфейс для создания процесса. Он не блокирует выполнение родительского скрипта, позволяя отправлять данные в stdin и читать данные из stdout/stderr по мере поступления.

import subprocess
proc = subprocess.Popen(['python', '-c', 'for i in range(5): print(i)'],
                       stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# Чтение построчно в реальном времени
for line in proc.stdout:
    print('Получено:', line.strip())
proc.wait()
print('Процесс завершён с кодом:', proc.returncode)

Python send files (отправка файлов в python)

Пояснение: stdout=subprocess.PIPE перенаправляет вывод в канал, который можно читать. Атрибут proc.stdout является файлоподобным объектом. Вызов proc.wait() дожидается завершения и предотвращает зомби-процессы.

Ошибки:

  • Если не вызывать proc.wait() или не закрыть потоки, процесс может остаться зомби.
  • При чтении без буферизации может возникнуть блокировка, если процесс ждёт ввода stdout (deadlock). Используйте communicate() или асинхронное чтение.
  • Работа с бинарными данными требует отключения text=True и ручного декодирования.

Цель: Необходим для длительных или интерактивных процессов, когда требуется постоянное взаимодействие (например, запуск интерпретатора команд или сервера).

Как просто выполнить команду без детального контроля над потоками?

Функция subprocess.call() (аналог старого os.system, но безопаснее) запускает процесс и ожидает его завершения. Возвращает код возврата.

import subprocess
ret = subprocess.call(['echo', 'Hello World'])
print('Возвращён', ret)

Python system path (системные вызовы и файловая система в python)

Вывод команды направляется в терминал родителя. Для подавления вывода можно перенаправить в subprocess.DEVNULL.

Цель: Быстрый запуск команды, когда не нужно анализировать её вывод (например, очистка временных файлов).

Как получить вывод команды и автоматически выбросить исключение при ошибке?

Функция subprocess.check_output() возвращает stdout, а при ненулевом коде возврата выбрасывает CalledProcessError.

import subprocess
try:
    output = subprocess.check_output(['ls', '/nonexistent'], stderr=subprocess.STDOUT, text=True)
except subprocess.CalledProcessError as e:
    print('Ошибка:', e.returncode, e.output)

Python py file system (работа с файловой системой в python (os, shutil))

Аргумент stderr=subprocess.STDOUT объединяет потоки. check_call() ведёт себя аналогично, но не возвращает stdout.

Цель: Когда нужен гарантированный успех команды, и при сбое требуется немедленная реакция (например, в скриптах сборки).

Как быстро выполнить команду через оболочку без забот о потоках?

Функция os.system(command) запускает строку в оболочке и возвращает код завершения (но не stdout/stderr).

import os
code = os.system('echo Привет из оболочки && date')
print('Код:', code)

Вывод печатается на консоль. Функция os.popen (устарела) может захватить вывод.

Проблемы:

  • Полная зависимость от системной оболочки, что создаёт риски инъекций.
  • Невозможность управлять потоками и таймаутами.
  • Платформозависимое поведение.

Цель: Только для самых простых однострочных команд в изолированных сценариях (например, внутри одного сервера с полностью контролируемым вводом).

В этом разделе рассмотрены менее очевидные, но полезные сценарии вызова внешних процессов, которые могут понадобиться в сложных системных задачах.

Продвинутые примеры вызова внешних программ

Как ограничить время выполнения команды с помощью таймаута?

Пример
import subprocess
try:
    result = subprocess.run(['sleep', '10'], timeout=3, capture_output=True, text=True)
except subprocess.TimeoutExpired:
    print('Процесс превысил время ожидания')
Процесс превысил время ожидания

Как асинхронно запустить процесс и дождаться его завершения без блокировки потока?

Пример
import asyncio
async def run_cmd():
    proc = await asyncio.create_subprocess_exec(
        'ls', '-la',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await proc.communicate()
    print('Stdout:', stdout.decode())
asyncio.run(run_cmd())
Stdout: (вывод ls -la)

Как организовать конвейер из нескольких команд (аналог shell pipe)?

Пример
import subprocess
p1 = subprocess.Popen(['cat', '/etc/passwd'], stdout=subprocess.PIPE)
p2 = subprocess.Popen(['grep', 'root'], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close()
output = p2.communicate()[0].decode()
print(output)
root:x:0:0:root:/root:/bin/bash

Как передать данные на вход программе через stdin и получить результат?

Пример
import subprocess
proc = subprocess.Popen(['grep', 'error'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
out, _ = proc.communicate('line1\nline2 error\nline3\n')
print(out.strip())
line2 error

Как читать вывод длительной программы построчно в реальном времени?

Пример
import subprocess
proc = subprocess.Popen(['ping', '-c', '3', 'google.com'], stdout=subprocess.PIPE, text=True)
for line in iter(proc.stdout.readline, ''):
    print('Получена строка:', line.strip())
proc.wait()
Получена строка: PING google.com (142.250.74.14) 56(84) bytes of data.
Получена строка: 64 bytes from ...
Получена строка: ...

Как запустить программу с изменённым окружением и рабочей директорией?

Пример
import subprocess, os
new_env = os.environ.copy()
new_env['MYVAR'] = 'custom'
result = subprocess.run(['printenv', 'MYVAR'], env=new_env, capture_output=True, text=True, cwd='/tmp')
print(result.stdout)
custom

Вызов программы из Python - comments

En
Python вызов программы (python)