python
August 29, 2023

Запуск инструкций командной строки из Python

Мостиком для взаимодействия Python и командной строки служит модуль subprocess. С его помощью можно выполнить команду и получить результат ее работы прямо из кода. Предпочтительным способом запуска является функция run.

command


По умолчанию первым аргументом run передается список из команды и ее параметров. Этого достаточно для запуска:

import subprocess

subprocess.run(['ls', '-la'])

shell


Также можно задать первый аргумент как строку, но тогда нужно указать флаг shell=True:

subprocess.run('ls -la')
subprocess.run('ls -la', shell=True)

capture_output

Опционально можно перехватить потоки вывода и ошибок путем установки флага capture_output=True:

res = subprocess.run('ls -la', shell=True, capture_output=True)
print(res.stdout.decode('utf8'))
print(res.returncode)

res = subprocess.run('ls w', shell=True, capture_output=True)
print(res.returncode)
print(res.stderr.decode('utf8'))

text


В stdout и stderr по умолчанию возвращается байтовые строки, которые надо декодировать. Иначе можно установить флаг text:

res = subprocess.run('ls -la', shell=True, capture_output=True)
print(res.stdout)
res = subprocess.run('ls -la', shell=True, text=True, capture_output=True)
print(res.stdout)

encoding


По умолчанию декодирование происходит с кодировкой, которую можно получить так:

import locale
locale.getpreferredencoding(False)

Иначе можно задать свою кодировку в параметре encoding.

timeout


Аргумент timeout задает максимальное время (в секундах) ожидания выполнения команды, после выбрасывается исключение TimeoutExpired:

subprocess.run('ls -l', shell=True, capture_output=True, timeout=0.1)
subprocess.run('ls -l', shell=True, capture_output=True, timeout=0.001)

check


Если установить флаг check, то при ненулевом коде возврата будет генерироваться исключение CalledProcessError:

subprocess.run('ls -l', shell=True, check=True)
subprocess.run('ls w', shell=True, check=True)

input


В параметре input можно задать значение, которое будет передано в stdin:

import sys

result = subprocess.run(
    [sys.executable, "-c", "import sys; print(sys.stdin.read())"], capture_output=True, text=True, input="hello"
)
result.stdout

Popen


Внутри run обращается к объекту класса Popen, который обладает более гибкими, но и сложными настройками:

res = subprocess.Popen('ls -la', shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True).communicate()
# stdout
print(res[0])
res = subprocess.Popen('ls w', shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True).communicate()
# stderr
print(res[1])

Интерфейс вызова через класс Popen немного отличается. Главное, что посредством вызова метода communicate запускается дочерней процесс, которому в stdin отправляются данные и считывается результат из stdout и stderr. Для взаимодействия с потоками ввода/вывода указывайте в соответствующих параметрах значение PIPE (метод run так и поступает при capture_output=True).