Методы выполнения команд через exec в Python для администрирования
Выполнение команд через exec в Python: полное руководство
Основное решение: subprocess.run (модуль subprocess)
Для выполнения внешних команд в Python наиболее эффективным и современным способом является использование класса subprocess.run. Он заменяет устаревшие os.system и os.popen, предоставляя гибкий контроль над вводом/выводом, обработку ошибок и безопасность.
Как выполнить простую команду и получить её код возврата?
Пример запуска команды ls в Linux:
import subprocess
result = subprocess.run(['ls', '-la'], capture_output=True, text=True)
print('Код возврата:', result.returncode)
print('stdout:', result.stdout)
print('stderr:', result.stderr)Cmd commands python (команды cmd в python)
Пояснение:
- Первый аргумент - список строк (программа и аргументы).
- capture_output=True перенаправляет stdout и stderr в объект результата.
- text=True возвращает строки вместо байтов.
- result.returncode содержит код завершения (0 - успех).
Распространённая ошибка: инъекция через shell=True
Если команда строится из внешних данных, использование shell=True крайне опасно. Злоумышленник может передать команду, которая выполнит произвольный код. Всегда предпочтительнее передавать список аргументов, избегая оболочки. Если shell=True неизбежен, необходимо тщательно экранировать входные данные с помощью shlex.quote().
import subprocess
import shlex
user_input = '; rm -rf /' # опасная строка
safe_cmd = 'echo ' + shlex.quote(user_input)
subprocess.run(safe_cmd, shell=True)Python exec command (выполнение команды через exec в python)
Вариант 1: os.system - простейший запуск
Функция os.system(command) выполняет команду в подоболочке и возвращает код завершения. Подходит для быстрых скриптов, где не нужен вывод.
Как запустить команду, игнорируя её вывод?
import os
exit_code = os.system('mkdir -p /tmp/test')
if exit_code == 0:
print('Директория создана')
else:
print('Ошибка, код:', exit_code)Python execute command line (выполнение команд командной строки из python)
- Проблема: вывод команды напрямую попадает в терминал, его нельзя перехватить.
- Решение: использовать subprocess или os.popen для захвата вывода.
- Ошибка: os.system не различает stdout и stderr, код возврата 0 не гарантирует успех.
Вариант 2: os.popen - чтение вывода команды
Как выполнить команду и прочитать её вывод?
import os
handle = os.popen('ls -la')
output = handle.read()
print(output)
handle.close()Python 3 commands (команды python 3)
Функция возвращает файлоподобный объект. Для записи в команду используется режим 'w'. Однако os.popen устарел, рекомендуется subprocess.Popen.
- Проблема: отсутствие контроля над stderr.
- Решение: использовать subprocess.Popen с параметрами stdout и stderr.
Вариант 3: 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('PING:', line.strip())
proc.wait()
print('Код:', proc.returncode)
Popen не блокирует выполнение, позволяя читать потоки по мере поступления. Require вызывает wait() для ожидания завершения.
- Проблема: deadlock при неполном чтении stdout/stderr.
- Решение: использовать communicate() или читать оба потока в отдельных потоках.
- Пример безопасного чтения:
proc = subprocess.Popen(['command'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
print('stdout:', out.decode() if out else '')
print('stderr:', err.decode() if err else '')
Вариант 4: семейство os.exec - замена процесса
Функции os.exec* (execl, execvp и др.) заменяют текущий процесс новой программой. Они не создают дочерний процесс и не возвращают управление в Python.
Как полностью передать управление другой программе (например, для запуска демона)?
import os
print('До exec')
os.execvp('nano', ['nano', '/tmp/test.txt'])
print('Этот код никогда не выполнится')
# После exec процесс Python завершается и заменяется nano
- Проблема: после exec нельзя вернуться к исходному Python-коду.
- Используется в сценариях-обёртках (wrapper) или для перезапуска скрипта с новыми аргументами.
- Важно: exec не порождает дочерний процесс, поэтому PID остаётся прежним.
Вариант 5: exec() - выполнение Python-кода (не системных команд)
Встроенная функция exec() выполняет динамический Python-код, переданный в виде строки. Это не системная команда, но часто путается с os.exec.
Как выполнить произвольный Python-код из строки?
code = '''
def hello():
print("Привет из exec!")
hello()
'''
exec(code)
# Вывод: Привет из exec!
- Опасность: exec может выполнить вредоносный код, если источник не доверенный.
- Рекомендация: избегать exec с пользовательскими данными, использовать eval с осторожностью или безопасные альтернативы (например, ast.literal_eval).
Расширенные примеры работы с командами
Ниже приведены нетривиальные сценарии выполнения команд через subprocess и другие методы.
1. Асинхронное выполнение с помощью asyncio
Как запустить команду без блокировки основной программы (асинхронно)?
import asyncio
async def run_cmd(cmd):
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
return proc.returncode, stdout.decode(), stderr.decode()
async def main():
code, out, err = await run_cmd(['ping', '-c', '2', '8.8.8.8'])
print(f'Код: {code}')
print(f'Вывод: {out}')
asyncio.run(main())
Результат:
Код: 0 Вывод: PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=12.3 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=118 time=12.1 ms --- 8.8.8.8 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms
2. Передача данных на stdin команды
Как передать строку в stdin внешней программы?
import subprocess
# Пример: шифрование строки через openssl
plain = b"secret data"
proc = subprocess.Popen(
['openssl', 'enc', '-aes-256-cbc', '-base64', '-pass', 'pass:mypassword'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
out, err = proc.communicate(input=plain)
print('Зашифровано:', out.decode())
print('Ошибки:', err.decode() if err else 'нет')
Результат:
Зашифровано: U2FsdGVkX1+... (зависит от данных) Ошибки: нет
3. Запуск с изменённым окружением (env)
Как передать собственные переменные окружения, не затрагивая внешнюю среду?
import subprocess
import os
my_env = {**os.environ, 'MY_VAR': 'hello'}
proc = subprocess.run(
['printenv', 'MY_VAR'],
env=my_env,
capture_output=True,
text=True
)
print('MY_VAR =', proc.stdout.strip())
Результат:
MY_VAR = hello
4. Перенаправление вывода в файл
Как сохранить stdout и stderr в разные файлы?
import subprocess
with open('stdout.log', 'w') as out_f, open('stderr.log', 'w') as err_f:
subprocess.run(
['ls', '/nonexistent'],
stdout=out_f,
stderr=err_f,
text=True
)
print('Вывод сохранён в файлы stdout.log и stderr.log')
Содержимое stderr.log:
ls: cannot access '/nonexistent': No such file or directory
5. Установка тайм-аута выполнения
Как прервать команду, если она работает дольше заданного времени?
import subprocess
try:
proc = subprocess.run(['sleep', '10'], timeout=3)
print('Команда завершилась вовремя')
except subprocess.TimeoutExpired:
print('Команда прервана по тайм-ауту')
Результат:
Команда прервана по тайм-ауту
6. Запуск команды с повышенными правами (sudo) с неинтерактивным паролем
Как выполнить команду от root, передав пароль через stdin?
import subprocess
password = 'my_sudo_pass'
cmd = ['sudo', '-S', 'whoami']
proc = subprocess.run(cmd, input=password + '\n', capture_output=True, text=True)
print(proc.stdout.strip()) # выведет 'root' при успехе
Внимание: хранение пароля в коде небезопасно, лучше использовать sudo с NOPASSWD или expect.