Интеграция Python и командной строки: практические решения для администрирования
Основные способы выполнения команд командной строки из Python
Выполнение команд операционной системы - одна из ключевых задач при автоматизации администрирования. Python предлагает несколько встроенных модулей, каждый из которых подходит для определённых сценариев. Наиболее современным и универсальным считается модуль subprocess, который и будет рассмотрен в качестве основного решения.
Как выполнить команду и получить её вывод, код возврата и обработать ошибки?
Модуль subprocess предоставляет функцию run(), которая запускает команду, ожидает её завершения и возвращает объект CompletedProcess. Рекомендуется использовать subprocess.run() везде, где не требуется асинхронное взаимодействие с процессом.
import subprocess
# Простейший запуск команды
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout) # стандартный вывод
print(result.stderr) # стандартный поток ошибок
print(result.returncode) # код возврата (0 - успех)Python execute command line (выполнение команд командной строки из python)
Аргумент capture_output=True перехватывает stdout и stderr. Если его не указать, вывод уйдёт непосредственно в терминал (полезно для интерактивных команд). Параметр text=True преобразует байтовые строки в обычные строки Python.
# Проверка кода возврата: если команда завершилась с ошибкой - поднимается исключение
result = subprocess.run(['false'], check=True) # вызовет CalledProcessErrorPython exec command (выполнение команды через exec в python)
Почему код возврата не 0, хотя команда выполнилась?
Некоторые утилиты возвращают ненулевой код при определённых условиях (например, grep возвращает 1, если ничего не найдено). Следует проверять логику команды, а не полагаться исключительно на check=True. Для анализа используйте result.returncode.
Как просто запустить команду без получения вывода (только выполнить)?
Если результат не важен, можно использовать устаревшую функцию os.system().
import os
os.system('mkdir /tmp/test')Cmd commands python (команды cmd в python)
Этот способ не возвращает вывод, только код возврата (число). Он удобен для быстрых однострочников, но небезопасен при передаче пользовательских данных (shell injection).
Почему возникает ошибка shell injection при использовании os.system()?
Если строка команды формируется из непроверенных данных (например, имя файла от пользователя), злоумышленник может вставить дополнительные команды через специальные символы (``;``, ``|`` и т.д.). Решение - использовать subprocess со списком аргументов, экранирование не требуется.
Как получить вывод команды построчно в реальном времени?
Для непрерывного чтения вывода (например, при запуске долгого процесса) удобен subprocess.Popen.
import subprocess
proc = subprocess.Popen(['ping', '-c', '4', 'google.com'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
for line in proc.stdout:
print(line.strip())
proc.wait() # дождаться завершенияPython 3 commands (команды python 3)
Такой подход позволяет обрабатывать каждую строку по мере её появления.
Почему чтение из proc.stdout блокируется, если буфер заполнен?
При большом объёме вывода процесс может заблокироваться, ожидая чтения. Решение - читать построчно в отдельном потоке или использовать асинхронное выполнение (asyncio.create_subprocess_exec).
Как выполнить команду с передачей данных на stdin?
Модуль subprocess позволяет передать данные в стандартный ввод процесса.
result = subprocess.run(['grep', 'error'],
input='System error occurred\nWarning: low memory',
capture_output=True,
text=True)
print(result.stdout) # выведет строку с 'error'Как получить только код возврата без захвата вывода?
Используйте os.popen() с последующим закрытием файлового объекта.
import os
handle = os.popen('ls /nonexistent')
handle.close() # возвращает код возвратаОднако os.popen() считается устаревшим, предпочтительнее subprocess.
Почему os.popen() иногда не позволяет правильно получить код возврата?
Метод close() возвращает None, если процесс уже завершился, или код возврата, но только при первом закрытии. Повторный вызов даёт None. Лучше сразу использовать subprocess.Popen с wait().
Как безопасно разбить строку команды на аргументы?
Модуль shlex разбирает строку как оболочка, но избегает shell injection.
import shlex
import subprocess
cmd = 'ls -l "/path/with spaces/file"'
args = shlex.split(cmd)
subprocess.run(args)Полезно при получении команды из конфигурационного файла.
Как выполнить команду в указанной директории?
Можно передать параметр cwd в subprocess.run().
subprocess.run(['pwd'], cwd='/tmp') # выведет /tmpКак задать переменные окружения для команды?
Используйте аргумент env.
subprocess.run(['echo', '$HOME'], env={'HOME': '/custom/home'}, shell=True)
# При shell=True строка интерпретируется оболочкой, что менее безопасноПочему переменные окружения не применяются, если shell=False?
При shell=False подмена окружения работает корректно, но переменные нельзя использовать внутри аргументов (например, $HOME не подставится, так как не запускается оболочка). Для подстановки используйте os.environ или передавайте значения напрямую.
Расширенные примеры использования subprocess
Ниже приведены более сложные и нестандартные сценарии, которые могут потребоваться при администрировании.
Асинхронное выполнение с asyncio
import asyncio
async def run_cmd():
proc = await asyncio.create_subprocess_exec(
'ping', '-c', '10', 'example.com',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
print(f'[stdout]\n{stdout.decode()}')
print(f'[stderr]\n{stderr.decode()}')
asyncio.run(run_cmd())[stdout] PING example.com (93.184.216.34) 56(84) bytes of data. 64 bytes from 93.184.216.34: icmp_seq=1 ttl=56 time=34.2 ms ... [stderr]
Подходит для параллельного запуска множества команд без блокировки основного потока.
Мониторинг процесса с таймаутом
import subprocess
try:
result = subprocess.run(['sleep', '10'], timeout=5, capture_output=True, text=True)
print('Команда завершилась вовремя')
except subprocess.TimeoutExpired:
print('Команда превысила лимит времени')Команда превысила лимит времени
Если процесс не завершился за 5 секунд, выбрасывается исключение TimeoutExpired.
Передача сложных данных через stdin (например, команда openssl)
import subprocess
data = 'testpass\n' # пароль для openssl
proc = subprocess.Popen(
['openssl', 'enc', '-aes-256-cbc', '-salt', '-pbkdf2', '-pass', 'stdin', '-in', '/dev/null', '-out', 'encrypted.bin'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate(input=data.encode())
print('stdout:', stdout)
print('stderr:', stderr)stdout: b'' stderr: b''
Пароль передаётся не как аргумент, а через стандартный ввод, что безопаснее.
Манипуляции с файловыми дескрипторами (открытие файлов для перенаправления)
import subprocess
with open('output.log', 'w') as f:
subprocess.run(['ls', '-la'], stdout=f, stderr=subprocess.STDOUT)Вывод команды и ошибки записываются в файл output.log.
Комбинирование нескольких команд через shell=True (с осторожностью)
subprocess.run('ls -l | grep py', shell=True, capture_output=True, text=True)'-rw-r--r-- 1 user user 123 Jan 1 12:00 script.py\n'
При shell=True аргумент передаётся как одна строка, и оболочка обрабатывает конвейеры. Однако это повышает риск shell injection. Использовать только с проверенными данными.