Щоб дещо розширити попередні відповіді тут, є ряд деталей, які зазвичай не помічаються.
- Віддаю перевагу
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?