Щоб дещо розширити попередні відповіді тут, є ряд деталей, які зазвичай не помічаються.
- Віддаю перевагу
subprocess.run()
більш subprocess.check_call()
і друзями в протягом subprocess.call()
більш ніж subprocess.Popen()
над os.system()
більшos.popen()
- Розуміти і, ймовірно, використовувати
text=True
, ака universal_newlines=True
.
- Зрозумійте значення
shell=True
або shell=False
і як це змінює котирування та наявність зручностей оболонки.
- Зрозумійте відмінності між
sh
Башем
- Зрозумійте, як підпроцес відокремлений від його батьківського і, як правило, не може змінити батьківський.
- Уникайте запуску інтерпретатора Python як підпроцесу Python.
Ці теми висвітлюються детальніше нижче.
Віддавайте перевагу subprocess.run()
абоsubprocess.check_call()
The 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 або взагалі рядки.
(Якщо відмінності не відразу очевидні, Прагматичний Unicode Неда Батчелдера рекомендується читати, якщо не прямо, обов'язково, читати. За посиланням стоїть 36-хвилинне відеопрезентація, хоч читання сторінки самостійно, ймовірно, займе значно менше часу. )
Глибоко вниз, Python повинен отримати bytes
буфер і якось його інтерпретувати. Якщо він містить фрагмент бінарних даних, його не слід розшифровувати у рядок Unicode, оскільки це схильна до помилок та поведінка викликає помилку - саме таку прискіпливу поведінку, яка позбавила багатьох сценаріїв Python 2, перш ніж з'явився спосіб правильно розрізняти закодований текст і бінарні дані.
З text=True
, ви говорите Python, що ви насправді очікуєте повернення текстових даних у кодування за замовчуванням системи, і що вони повинні бути декодовані в рядок Python (Unicode) якнайкраще з можливостей Python (як правило, UTF-8 на будь-яких помірно до система дат, крім можливо Windows?)
Якщо це не те, що ви запитуєте назад, Python просто надасть вам bytes
рядки в рядках stdout
і stderr
рядках. Може бути , в якій - то пізніше ви ж знаєте , що вони були текстові рядки в кінці кінців, і ви знаєте , їх кодування. Потім ви можете їх розшифрувати.
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 можна замінити
glob.glob()
або дуже часто простими порівняннями рядків Python for file in os.listdir('.'): if not file.endswith('.png'): continue
. Bash має різноманітні інші засоби розширення, такі як .{png,jpg}
розширення брекетів {1..100}
, а також розширення нахилу ( ~
розширюється до домашнього каталогу та загалом ~account
до домашнього каталогу іншого користувача)
- Змінні оболонки на кшталт
$SHELL
або $my_exported_var
іноді просто можуть бути замінені змінними Python. Експортовані змінні оболонки доступні як наприклад os.environ['SHELL']
(сенс export
полягає в тому, щоб зробити змінну доступною для підпроцесів - змінна, яка недоступна для підпроцесів, очевидно, буде недоступною для Python, що працює як підпроцес оболонки, або навпаки. env=
Ключове слово Аргумент subprocess
методам дозволяє визначити середовище підпроцесу як словник, тому це один із способів зробити змінну Python видимою для підпроцесу). З shell=False
вами потрібно буде зрозуміти, як видалити будь-які лапки; наприклад, cd "$HOME"
еквівалент os.chdir(os.environ['HOME'])
без лапок навколо імені каталогу. (Дуже частоcd
так чи інакше не є корисним або необхідним, і багато початківців опускають подвійні лапки навколо змінної і відходять від неї до одного дня ... )
- Перенаправлення дозволяє читати з файлу як ваш стандартний вхід і записувати стандартний вихід у файл.
grep 'foo' <inputfile >outputfile
відкривається outputfile
для запису та inputfile
для читання і передає його вміст як стандартний вхід grep
, стандартний вихід якого потім приземляється outputfile
. Зазвичай це важко замінити нативним кодом Python.
- Трубопроводи - це форма перенаправлення.
echo foo | nl
запускає два підпроцеси, де стандартний вихід echo
- це стандартний вхід nl
(на рівні ОС, в Unix-подібних системах, це одна ручка файлу). Якщо ви не можете замінити один або обидва кінці конвеєра нативним кодом Python, можливо, подумайте про використання оболонки зрештою, особливо якщо трубопровід має більше двох-трьох процесів (хоча подивіться на pipes
модуль у стандартній бібліотеці Python або номер більш сучасних та універсальних сторонніх конкурентів).
- Контроль за роботою дозволяє вам переривати завдання, запускати їх у фоновому режимі, повертати їх на перший план тощо. Основні сигнали Unix для зупинки та продовження процесу, звичайно, доступні і від Python. Але завдання - це абстракція вищого рівня в оболонці, яка включає групи процесів тощо, які ви повинні зрозуміти, якщо ви хочете зробити щось подібне з Python.
- Цитування в оболонці потенційно плутає, поки ви не зрозумієте, що все в основному є рядком. Так що
ls -l /
рівнозначно, 'ls' '-l' '/'
але цитування навколо літералів абсолютно необов'язково. Рядки без котирування, що містять метахарактори оболонки, зазнають розширення параметрів, токенізації пробілів та розширення підстановки; подвійні лапки запобігають токенізації пробілів та розширенню підстановки, але дозволяють розширювати параметри (заміна змінної, заміна команд та обробка зворотної косої риски). Теоретично це просто, але може набути здивування, особливо якщо є кілька шарів інтерпретації (наприклад, віддалена команда оболонки).
Зрозумійте відмінності між sh
Башем
subprocess
запускає ваші команди оболонки, /bin/sh
якщо ви спеціально не вимагаєте іншого (за винятком, звичайно, в Windows, де він використовує значення COMSPEC
змінної). Це означає, що різні функції Bash, лише масиви [[
тощо , недоступні.
Якщо вам потрібно використовувати синтаксис лише для Bash, ви можете передати шлях до оболонки як 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 .)
cwm
. Можливо, у вас є якась конфігурація,.bashrc
яка створює середовище для інтерактивного використання bash?