Получение результатов внешних команд через 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%'}]