Чтобы немного расширить предыдущие ответы, есть ряд деталей, которые обычно упускаются из виду.
- Предпочитают
subprocess.run()
более subprocess.check_call()
и друзей более subprocess.call()
более subprocess.Popen()
более os.system()
более os.popen()
- Понять и, вероятно, использовать
text=True
, он же universal_newlines=True
.
- Понять значение
shell=True
или shell=False
и то, как оно меняет цитирование и доступность удобства оболочки.
- Понять разницу между
sh
и Bash
- Понять, как подпроцесс отделен от своего родителя и, как правило, не может изменить родителя.
- Избегайте запуска интерпретатора Python как подпроцесса Python.
Эти темы более подробно описаны ниже.
Предпочитают subprocess.run()
или subprocess.check_call()
Функция subprocess.Popen()
- это рабочая лошадка низкого уровня, но ее сложно использовать правильно, и в итоге вы копируете / вставляете несколько строк кода ... которые обычно уже существуют в стандартной библиотеке как набор более высокого уровня Функции-оболочки для различных целей, которые более подробно представлены ниже.
Вот параграф из документации :
Рекомендуемый подход к вызову подпроцессов заключается в использовании функции run()
для всех случаев использования, которые он может обработать. Для более сложных вариантов использования базовый интерфейс Popen
может использоваться напрямую.
К сожалению, доступность этих функций-оболочек отличается в разных версиях Python.
subprocess.run()
был официально представлен в Python 3.5. Он предназначен для замены всего следующего.
subprocess.check_output()
был представлен в Python 2.7 / 3.1. Это в основном эквивалентно subprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
был представлен в Python 2.5. Это в основном эквивалентно subprocess.run(..., check=True)
subprocess.call()
был представлен в Python 2.4 в оригинальном модуле subprocess
( PEP-324 ). Это в основном эквивалентно subprocess.run(...).returncode
API высокого уровня против subprocess.Popen()
Реорганизованный и расширенный subprocess.run()
более логичен и более универсален, чем старые устаревшие функции, которые он заменяет. Он возвращает объект CompletedProcess
, который имеет различные методы, позволяющие получить состояние завершения, стандартный вывод и несколько других результатов и индикаторов состояния из готового подпроцесса.
subprocess.run()
- это путь, если вам просто нужна программа для запуска и возврата управления в Python. Для более сложных сценариев (фоновые процессы, возможно, с интерактивным вводом-выводом с родительской программой Python) вам все равно нужно использовать subprocess.Popen()
и самостоятельно позаботиться обо всем. Это требует довольно сложного понимания всех движущихся частей и не должно быть предпринято легко. Более простой Popen
объект представляет (возможно, все еще работающий) процесс, которым нужно управлять из вашего кода в течение оставшейся части времени жизни подпроцесса.
Следует, вероятно, подчеркнуть, что просто subprocess.Popen()
просто создает процесс. Если вы оставите все как есть, у вас будет подпроцесс, работающий одновременно с Python, так что это «фоновый» процесс. Если ему не нужно вводить или выводить или иным образом координировать с вами, он может выполнять полезную работу параллельно с вашей программой на Python.
Избегайте os.system()
и os.popen()
Начиная с вечного времени (ну, начиная с Python 2.5) документация os
модуля содержит рекомендацию предпочитать subprocess
над os.system()
:
Модуль subprocess
предоставляет более мощные средства для запуска новых процессов и получения их результатов; использование этого модуля предпочтительнее, чем использование этой функции.
Проблемы с system()
заключаются в том, что он явно зависит от системы и не предлагает способов взаимодействия с подпроцессом. Он просто работает со стандартным выводом и стандартной ошибкой вне досягаемости Python. Единственная информация, которую Python получает обратно, - это состояние завершения команды (ноль означает успех, хотя значение ненулевых значений также в некоторой степени зависит от системы).
PEP-324 (о котором уже упоминалось выше) содержит более подробное обоснование того, почему os.system
проблематичен и как subprocess
пытается решить эти проблемы.
os.popen()
раньше было еще больше настоятельно не рекомендуется :
Устаревшее с версии 2.6: Эта функция устарела. Используйте модуль subprocess
.
Однако с тех пор в Python 3 было переопределено простое использование subprocess
, и подробности перенаправляются в документацию subprocess.Popen()
.
Понимаю и обычно использую check=True
Вы также заметите, что subprocess.call()
имеет много тех же ограничений, что и os.system()
. При регулярном использовании обычно следует проверять, успешно ли завершен процесс, что делают subprocess.check_call()
и subprocess.check_output()
(где последний также возвращает стандартный вывод завершенного подпроцесса). Точно так же вы обычно должны использовать check=True
с subprocess.run()
, если вам не требуется, чтобы подпроцесс возвращал состояние ошибки.
На практике с check=True
или subprocess.check_*
Python сгенерирует CalledProcessError
исключение , если подпроцесс вернет ненулевой статус выхода.
Распространенная ошибка с subprocess.run()
- пропуск check=True
и удивление при сбое нижестоящего кода в случае сбоя подпроцесса.
С другой стороны, общая проблема с check_call()
и check_output()
заключалась в том, что пользователи, которые слепо использовали эти функции, были удивлены, когда возникло исключение, например. когда grep
не нашел соответствия. (В любом случае, вам, вероятно, следует заменить grep
на собственный код Python, как показано ниже.)
С учетом всего, вам нужно понять, как команды оболочки возвращают код выхода и при каких условиях они возвращают ненулевой (ошибочный) код выхода, и принимать осознанное решение, как именно его следует обрабатывать.
Понять и, вероятно, использовать text=True
иначе universal_newlines=True
Начиная с Python 3, строки внутри Python являются строками Unicode. Но нет никакой гарантии, что подпроцесс генерирует выходные данные Unicode или строки вообще.
(Если различия не сразу очевидны, рекомендуется Pragmatic Unicode Неда Батчелдера, если не прямо, то чтение. За ссылкой, если хотите, есть 36-минутная видеопрезентация, хотя вы и читаете Сама страница, вероятно, займет значительно меньше времени.)
В глубине души Python должен получить буфер bytes
и как-то его интерпретировать. Если он содержит двоичный объект двоичных данных, его не следует декодировать в строку Unicode, потому что это склонно к ошибкам и вызывает ошибки - именно такого рода надоедливое поведение, которое пронизывает многие скрипты Python 2, прежде чем был способ правильно провести различие между закодированным текстом и двоичными данными.
Используя text=True
, вы сообщаете Python, что фактически ожидаете возврата текстовых данных в кодировке системы по умолчанию и что они должны быть декодированы в строку Python (Unicode) в меру возможностей Python (обычно UTF- 8 на любой умеренно современной системе, кроме, возможно, Windows?)
Если это , а не , что вы запрашиваете обратно, Python просто выдаст вам bytes
строки в строках stdout
и stderr
. Может быть, в какой-то момент вы do знаете, что в конце концов они были текстовыми строками, и вы знаете их кодировку. Затем вы можете их декодировать.
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
В Python 3.7 введен более короткий и более описательный и понятный псевдоним text
для аргумента ключевого слова, который ранее несколько вводил в заблуждение как universal_newlines
.
Понять shell=True
против shell=False
С помощью shell=True
вы передаете одну строку в вашу оболочку, и оболочка забирает ее оттуда.
С помощью shell=False
вы передаете список аргументов ОС, минуя оболочку.
Когда у вас нет оболочки, вы сохраняете процесс и избавляетесь от довольно значительного количества скрытой сложности, которая может или не может содержать ошибки или даже проблемы безопасности.
С другой стороны, когда у вас нет оболочки, у вас нет перенаправления, подстановочного знака, управления заданиями и большого количества других функций оболочки.
Распространенной ошибкой является использование shell=True
, а затем передача Python списка токенов или наоборот. В некоторых случаях это срабатывает, но на самом деле плохо определено и может ломаться интересными способами.
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
Общая реплика «но это работает для меня» не является полезным опровержением, если вы точно не понимаете, при каких обстоятельствах она может перестать работать.
Пример рефакторинга
Очень часто функции оболочки могут быть заменены собственным кодом Python. Простые сценарии Awk или sed
, вероятно, должны быть просто переведены на Python.
Чтобы частично проиллюстрировать это, вот типичный, но немного глупый пример, который включает в себя множество функций оболочки.
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
Некоторые вещи, на которые стоит обратить внимание:
- С
shell=False
вам не нужно цитирование, которое требуется оболочке вокруг строк. В любом случае помещать кавычки - это ошибка.
- Часто имеет смысл запускать как можно меньше кода в подпроцессе. Это дает вам больше контроля над выполнением из вашего кода Python.
- Сказав это, сложные конвейеры оболочки утомительны, а иногда и трудны для переопределения в Python.
Реорганизованный код также показывает, насколько действительно полезна оболочка для вас с очень кратким синтаксисом - к лучшему или к худшему. Python говорит, что явное лучше, чем неявное , но код Python является довольно многословным и, возможно, выглядит более сложным, чем на самом деле. С другой стороны, он предлагает ряд точек, в которых вы можете захватить контроль посреди чего-то другого, о чем тривиально свидетельствует улучшение, которое мы можем легко включить в имя хоста вместе с выводом команды оболочки. (Это ни в коем случае не является сложной задачей в оболочке, но за счет еще одной утечки и, возможно, другого процесса.)
Общие конструкции оболочки
Для полноты изложения приведем краткие пояснения некоторых из этих функций оболочки и некоторые примечания о том, как их можно заменить собственными средствами Python.
- Расширение Globbing aka wildcard можно заменить на
glob.glob()
или очень часто простым сравнением строк Python, таким как for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash имеет различные другие возможности расширения, такие как .{png,jpg}
расширение скобок и {1..100}
, а также расширение тильды (~
расширяется до вашего домашнего каталога и, в более общем случае, ~account
до домашнего каталога другого пользователя)
- Перенаправление позволяет читать из файла в качестве стандартного ввода и записывать стандартный вывод в файл.
grep 'foo' <inputfile >outputfile
открывает outputfile
для записи и inputfile
для чтения и передает его содержимое в качестве стандартного ввода в grep
, чей стандартный вывод затем попадает в outputfile
. Обычно это не сложно заменить на собственный код Python.
- Трубопроводы - это форма перенаправления.
echo foo | nl
запускает два подпроцесса, где стандартный вывод echo
является стандартным вводом nl
(на уровне ОС, в Unix-подобных системах это дескриптор одного файла). Если вы не можете заменить один или оба конца конвейера собственным кодом Python, возможно, в конце концов подумайте об использовании оболочки, особенно если конвейер содержит более двух или трех процессов (хотя посмотрите на модуль pipes
в Стандартная библиотека Python или ряд более современных и универсальных сторонних конкурентов).
- Управление заданиями позволяет прерывать задания, запускать их в фоновом режиме, возвращать их на передний план и т. Д. Основные сигналы Unix для остановки и продолжения процесса, конечно, доступны и в Python. Но задания - это высокоуровневая абстракция в оболочке, которая включает группы процессов и т. Д., Которые вы должны понимать, если хотите сделать что-то подобное из Python.
Понять разницу между sh
и Bash
subprocess
запускает ваши команды оболочки с /bin/sh
, если вы специально не запрашиваете иное (кроме, конечно, в Windows, где он использует значение переменной COMSPEC
).Это означает, что различные функции Bash-only, такие как массивы, [[
и т. Д. , недоступны.
Если вам нужно использовать синтаксис Bash-only, вы можете передать путь к оболочкекак executable='/bin/bash'
(где, конечно, если ваш Bash установлен где-то еще, вам нужно изменить путь).
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocess
отделен от своего родителя и не может изменить его
Несколько распространенная ошибка - делать что-то вроде
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
, что, помимо недостатка элегантности, также выдает принципиальное непонимание "под" части названия "подпроцесс".
Дочерний процесс запускается совершенно отдельно от Python, и когда он завершается, Python понятия не имеет, что он сделал (кроме неопределенных индикаторов, которые он может вывести из состояния выхода и вывода из дочернего процесса).Ребенок обычно не может изменить среду родителей;он не может установить переменную, изменить рабочий каталог или, во многих словах, связаться со своим родителем без сотрудничества с родителем.
Немедленное исправление в этом конкретном случае - запустить обе команды в одном подпроцессе.;
subprocess.run('foo=bar; echo "$foo"', shell=True)
хотя очевидно, что этот конкретный вариант использования вообще не требует оболочки.Помните, что вы можете манипулировать средой текущего процесса (и, следовательно, также его дочерних элементов) через
os.environ['foo'] = 'bar'
или передать настройку среды дочернему процессу с помощью
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(неупомяните очевидный рефакторинг subprocess.run(['echo', 'bar'])
, но, конечно, echo
- плохой пример того, что нужно запускать в подпроцессе.
Не запускать Python из Python
Это немного сомнительный совет;безусловно, существуют ситуации, когда это имеет смысл или даже является абсолютным требованием для запуска интерпретатора Python в качестве подпроцесса из сценария Python.Но очень часто правильным подходом является просто import
другой модуль Python в ваш вызывающий скрипт и прямой вызов его функций.
Если другой скрипт Python находится под вашим контролем, и это не модульрассмотрим превращение его в один .(Этот ответ уже слишком длинный, поэтому я не буду вдаваться в подробности.)
Если вам нужен параллелизм, вы можете запускать функции Python в подпроцессах с модулем multiprocessing
. Естьтакже threading
, который выполняет несколько задач в одном процессе (который является более легким и дает вам больше контроля, но также более ограничен в том, что потоки в процессе тесно связаны и связаны с одним GIL .)