Получение результатов внешних команд через subprocess в Python

Раздел: Администрирование -> Запуск внешних процессов

Основной способ: subprocess.run с capture_output

Как получить stdout и stderr от запущенного процесса?

Модуль subprocess предоставляет функцию run(), которая позволяет захватить вывод. Параметр capture_output=True перенаправляет stdout и stderr в объект CompletedProcess, после чего они доступны как строки через атрибуты stdout и stderr.

import subprocess
result = subprocess.run(['ls', '-la'], capture_output=True, text=True)
print('stdout:', result.stdout)
print('stderr:', result.stderr)
print('return code:', result.returncode)

Python subprocess output (вывод из subprocess в python)

Пример вывода:

stdout: total 32
drwxr-xr-x ...
stderr:
return code: 0

Если требуется обработать вывод в реальном времени, следует использовать Popen.

Проблема: при использовании capture_output и большом объеме вывода может произойти переполнение буфера. Решение - перенаправлять вывод в файл или использовать Popen с поточным чтением.

Типичная ошибка: UnicodeDecodeError при чтении бинарных данных. Установка text=True включает декодирование по умолчанию в UTF-8, но можно указать encoding или errors параметр.

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

Для чтения вывода в реальном времени используется subprocess.Popen с циклом построчного чтения из stdout. Это полезно для длительных процессов, где требуется мониторинг.

import subprocess
process = subprocess.Popen(['ping', '-c', '4', 'google.com'], stdout=subprocess.PIPE, text=True)
for line in process.stdout:
    print(line.strip())
process.wait()
PING google.com (142.250.185.78): 56 data bytes
64 bytes from 142.250.185.78: icmp_seq=0 ttl=118 time=11.2 ms
64 bytes from 142.250.185.78: icmp_seq=1 ttl=118 time=10.8 ms
64 bytes from 142.250.185.78: icmp_seq=2 ttl=118 time=11.0 ms
64 bytes from 142.250.185.78: icmp_seq=3 ttl=118 time=11.1 ms

--- google.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
...

Проблема: зависание при чтении, если процесс не закрывает stdout. Решение - использовать timeout или читать только определенное количество строк.

Как объединить stdout и stderr в один поток?

Параметр stderr=subprocess.STDOUT перенаправляет stderr в stdout, позволяя прочитать все сообщения через один поток.

result = subprocess.run(['grep', 'pattern', 'nonexistent_file'], capture_output=True, stderr=subprocess.STDOUT, text=True)
print(result.stdout)
grep: nonexistent_file: No such file or directory

Проблема: порядок строк может быть нарушен, если stderr и stdout буферизируются независимо.

Как проверить код возврата без исключения?

Если параметр check=True не указан, run() не вызывает исключение при ненулевом коде возврата. Атрибут returncode позволяет вручную проверить успешность.

result = subprocess.run(['false'], capture_output=True)
if result.returncode != 0:
    print('Процесс завершился с ошибкой', result.returncode)
Процесс завершился с ошибкой 1

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

По умолчанию capture_output возвращает байты. Если задать text=False, то stdout и stderr будут объектами bytes. Декодирование выполняется вручную с учетом кодировки вывода.

result = subprocess.run(['echo', 'тест'], capture_output=True, text=False)
output = result.stdout.decode('utf-8')
print(output)
тест

Проблема: неправильная кодировка приведет к ошибке декодирования. Рекомендуется указывать encoding в run().

Как передать данные на stdin процесса и получить вывод?

Параметр input в run() или communicate(input=...) в Popen позволяет отправить данные на стандартный ввод процесса.

result = subprocess.run(['cat'], input='Привет, subprocess!', capture_output=True, text=True)
print(result.stdout)
Привет, subprocess!

Проблема: при большом объеме input возможна блокировка. communicate() считывает весь вывод после отправки.

Как использовать subprocess.check_output для простого случая?

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

try:
    output = subprocess.check_output(['whoami'], text=True)
    print('Текущий пользователь:', output.strip())
except subprocess.CalledProcessError as e:
    print('Ошибка:', e.stderr)
Текущий пользователь: root

Проблема: check_output не поддерживает параметр input (требуется stdin).

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

Параметр shell=True передает команду в виде строки оболочке (sh). Это удобно, но несет риски безопасности при подстановке переменных.

result = subprocess.run('ls -la | wc -l', shell=True, capture_output=True, text=True)
print('Количество строк:', result.stdout.strip())
Количество строк: 5

Проблема: инъекция команд при использовании shell=True с внешними данными. Рекомендуется избегать shell=True, если это возможно.

Расширенные примеры работы с выводом subprocess

Чтение вывода с таймаутом при помощи Popen

Для предотвращения зависания можно использовать метод wait(timeout=...) или communicate(timeout=...). Пример с timeout и обработкой исключения TimeoutExpired:

Пример
import subprocess
import time

try:
    process = subprocess.Popen(['sleep', '10'], stdout=subprocess.PIPE, text=True)
    stdout, stderr = process.communicate(timeout=3)
    print('Вывод:', stdout)
except subprocess.TimeoutExpired:
    process.kill()  # принудительное завершение
    print('Процесс превысил время ожидания и был завершен')
Процесс превысил время ожидания и был завершен

Комбинированный вывод stdout и stderr в реальном времени с метками

Для разделения сообщений из разных потоков можно запустить два потока чтения. В примере используются потоки и очередь для асинхронного сбора:

Пример
import subprocess
import threading
import queue

def reader(stream, q, name):
    for line in iter(stream.readline, ''):
        q.put(f'{name}: {line.strip()}')
    stream.close()

process = subprocess.Popen(
    ['bash', '-c', 'echo stdout1; echo stderr1 >&2; echo stdout2'],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

q = queue.Queue()
th1 = threading.Thread(target=reader, args=(process.stdout, q, 'STDOUT'))
th2 = threading.Thread(target=reader, args=(process.stderr, q, 'STDERR'))
th1.start()
th2.start()
th1.join()
th2.join()
process.wait()

while not q.empty():
    print(q.get())
STDOUT: stdout1
STDERR: stderr1
STDOUT: stdout2

Перехват вывода с помощью временного файла

Если объем вывода очень велик, лучше направить его в файл, а не в память. subprocess позволяет использовать файловые объекты:

Пример
import subprocess
import tempfile

with tempfile.NamedTemporaryFile(mode='w+', delete=False) as f:
    process = subprocess.Popen(['find', '/usr', '-name', '*.py'], stdout=f, stderr=subprocess.STDOUT)
    process.wait()
    f.seek(0)
    # Можно читать построчно или весь вывод
    count = sum(1 for _ in f)
    print(f'Найдено {count} файлов .py')
Найдено 245 файлов .py

Использование subprocess.DEVNULL для игнорирования вывода

Если вывод не нужен, его можно направить в subprocess.DEVNULL для экономии ресурсов. Это часто используется в командах, где важен только код возврата:

Пример
import subprocess
result = subprocess.run(['rm', '-rf', '/tmp/test_dir'], capture_output=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if result.returncode == 0:
    print('Удаление прошло успешно')
Удаление прошло успешно

Продвинутая работа с буферизацией: отключение буферизации через PYTHONUNBUFFERED

При запуске интерпретатора Python через subprocess часто возникает проблема с буферизацией вывода. Установка переменной окружения PYTHONUNBUFFERED=1 отключает буферизацию:

Пример
import subprocess
import os

env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
process = subprocess.Popen(
    ['python3', '-c', 'import sys; print("строка 1"); sys.stderr.write("ошибка\n")'],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,
    env=env
)
stdout, stderr = process.communicate()
print('stdout:', stdout)
print('stderr:', stderr)
stdout: строка 1
stderr: ошибка

Получение вывода с помощью асинхронного subprocess (asyncio.create_subprocess_exec)

В асинхронном коде удобно использовать asyncio.create_subprocess_exec. Вывод считывается через корутины:

Пример
import asyncio

async def get_output():
    process = await asyncio.create_subprocess_exec(
        'git', 'log', '--oneline', '-1',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await process.communicate()
    print('Последний коммит:', stdout.decode().strip())

asyncio.run(get_output())
Последний коммит: a1b2c3d Исправление бага

Парсинг вывода команды df с помощью subprocess

Пример сложного анализа вывода: получение информации о дисковом пространстве и преобразование в словарь:

Пример
import subprocess
import shlex

result = subprocess.run(['df', '-h', '--output=target,size,used,avail,pcent'], capture_output=True, text=True)
lines = result.stdout.strip().split('\n')
headers = lines[0].split()
data = []
for line in lines[1:]:
    parts = line.split(maxsplit=4)
    if len(parts) == 5:
        data.append(dict(zip(headers, parts)))
print(data)
[{'Mounted': '/', 'Size': '27G', 'Used': '15G', 'Avail': '11G', 'Use%': '59%'}, {'Mounted': '/home', 'Size': '50G', 'Used': '20G', 'Avail': '30G', 'Use%': '40%'}]

вывод из subprocess в Python - comments

En
Python subprocess output (python)