Как запустить фоновый процесс в Python?

295

Я пытаюсь портировать сценарий оболочки на гораздо более читаемую версию Python. Оригинальный сценарий оболочки запускает несколько процессов (утилиты, мониторы и т. Д.) В фоновом режиме с «&». Как я могу добиться того же эффекта в Python? Я бы хотел, чтобы эти процессы не умирали после завершения сценариев Python. Я уверен, что это как-то связано с концепцией демона, но я не мог найти, как это легко сделать.

Артем
источник
1
Действительно дублированный вопрос: как запустить и запустить внешний скрипт в фоновом режиме? , Cheers;)
olibre
1
Привет Артем. Пожалуйста, примите ответ Дэна, потому что (1) больше голосов, (2) subprocess.Popen()это новый рекомендуемый способ с 2010 года (сейчас 2015 год) и (3) дублирующий вопрос, перенаправляющий сюда, также имеет принятый ответ subprocess.Popen(). Приветствия :-)
olibre
1
@olibre На самом деле ответ должен быть subprocess.Popen("<command>")с файлом <command> во главе с подходящим шебангом. Идеально подходит для меня (Debian) со скриптами bash и python, косвенно shells и выживает в родительском процессе. stdoutидет к тому же терминалу, что и родительский. Так что это работает так же, как &в оболочке, которая была запросом OP. Но, черт возьми, все вопросы прорабатываются очень сложно, в то время как небольшое тестирование показало это в
кратчайшие
Для фона, возможно, см. Также stackoverflow.com/a/51950538/874188
tripleee

Ответы:

84

Примечание . Этот ответ является менее актуальным, чем тот, который был опубликован в 2009 году. subprocessТеперь в документах рекомендуется использовать модуль, показанный в других ответах.

(Обратите внимание, что модуль подпроцесса предоставляет более мощные средства для порождения новых процессов и получения их результатов; использование этого модуля предпочтительнее использования этих функций.)


Если вы хотите, чтобы ваш процесс запускался в фоновом режиме, вы можете либо использовать system()и вызывать его так же, как это делал ваш сценарий оболочки, либо вы можете spawnэто сделать:

import os
os.spawnl(os.P_DETACH, 'some_long_running_command')

(или, альтернативно, вы можете попробовать менее переносимый os.P_NOWAITфлаг).

Смотрите документацию здесь .

JKP
источник
8
Примечание: вы должны указать полный путь к исполняемому файлу. Эта функция не будет использовать переменную PATH, и вариант, который ее использует, недоступен в Windows.
сорин
36
прямо крушится питон для меня.
Пабло
29
os.P_DETACH был заменен на os.P_NOWAIT.
себя
7
Могут ли люди, предлагающие использовать, subprocessподсказать нам, как отсоединить процесс subprocess?
Ракслице
3
Как я могу использовать скрипт Python (скажем, attach.py), чтобы найти фоновый процесс и перенаправить его ввод-вывод, чтобы attach.py ​​мог читать / писать в some_long_running_prog в фоновом режиме?
raof01
376

В то время как решение jkp работает, более новый способ работы (и способ, рекомендуемый документацией) состоит в том, чтобы использовать subprocessмодуль. Для простых команд это эквивалентно, но предлагает больше вариантов, если вы хотите сделать что-то сложное.

Пример для вашего случая:

import subprocess
subprocess.Popen(["rm","-r","some.file"])

Это будет работать rm -r some.fileв фоновом режиме. Обратите внимание, что вызов .communicate()объекта, возвращаемого из, Popenбудет блокироваться до его завершения, поэтому не делайте этого, если хотите, чтобы он работал в фоновом режиме:

import subprocess
ls_output=subprocess.Popen(["sleep", "30"])
ls_output.communicate()  # Will block for 30 seconds

Смотрите документацию здесь .

Кроме того, пояснение: «Фон», как вы его здесь используете, является чисто концепцией оболочки; технически, вы имеете в виду, что хотите запустить процесс без блокировки, пока вы ждете его завершения. Тем не менее, я использовал здесь «background» для обозначения поведения, похожего на shell-background.

Дэн
источник
7
@Dan: Как мне убить процесс, когда он работает в фоновом режиме? Я хочу запустить его на некоторое время (это демон, с которым я взаимодействую) и убить его, когда я закончу с ним. Документы не полезны ...
Хуан
1
@ Дэн, но разве мне не нужно знать PID для этого? Монитор активности / Диспетчер задач не вариант (должен происходить программно).
Хуан
5
Итак, как вы заставляете процесс работать в фоновом режиме, когда вам нужен результат Popen () для записи в его стандартный ввод?
Майкл
2
@JFSebastian: я интерпретировал это как «как я могу создать независимый процесс, который не останавливает выполнение текущей программы». Как бы вы предложили мне отредактировать это, чтобы сделать это более явным?
Дэн
1
@Dan: правильный ответ: используйте, Popen()чтобы избежать блокировки основного потока, и если вам нужен демон, посмотрите на python-daemonпакет, чтобы понять, как должен вести себя четко определенный демон. Ваш ответ в порядке, если вы удалите все, начиная с «Но будьте осторожны», за исключением ссылки на документы подпроцесса.
JFS
47

Вы, вероятно, хотите получить ответ на вопрос «Как вызвать внешнюю команду в Python» .

Самый простой подход - использовать os.systemфункцию, например:

import os
os.system("some_command &")

По сути, все, что вы передадите в systemфункцию, будет выполнено так же, как если бы вы передали ее оболочке в скрипте.

Эли Кортрайт
источник
10
ИМХО, скрипты на python обычно пишутся для кроссплатформенности, и если существует простое кроссплатформенное решение, лучше придерживаться его. Никогда не знаешь, придется ли тебе работать с другой платформой в будущем :) Или кто-то другой захочет перенести твой скрипт на свою платформу
d9k
7
Эта команда является синхронной (т.е. она всегда ожидает завершения запущенного процесса).
тав
@ d9k os.system не переносимая?
lucid_dreamer
1
@ d9k - разве не стоит запускать что-то в фоновом режиме, что уже позиционирует вас в posix-стране? Что бы вы сделали на Windows? Запускать как сервис?
lucid_dreamer
как я могу использовать это, если мне нужно запустить команду из определенной папки?
mrRobot
29

Я нашел это здесь :

В windows (win xp) родительский процесс не завершится, пока longtask.pyне завершит свою работу. Это не то, что вы хотите в CGI-скрипте. Проблема не является специфичной для Python, в сообществе PHP проблемы одинаковы.

Решение состоит в том, чтобы передать DETACHED_PROCESS флаг создания процесса основной CreateProcessфункции в Win API. Если вы установили pywin32, вы можете импортировать флаг из модуля win32process, в противном случае вы должны определить его самостоятельно:

DETACHED_PROCESS = 0x00000008

pid = subprocess.Popen([sys.executable, "longtask.py"],
                       creationflags=DETACHED_PROCESS).pid
Ф.П.
источник
6
+1 за показ, как сохранить идентификатор процесса. И если кто-то захочет позже убить программу с помощью идентификатора процесса: stackoverflow.com/questions/17856928/…
iChux
1
Это похоже только на Windows
Audrius Meskauskas
24

Используйте subprocess.Popen()с close_fds=Trueпараметром, который позволит отделить порожденный подпроцесс от самого процесса Python и продолжить работу даже после выхода из Python.

https://gist.github.com/yinjimmy/d6ad0742d03d54518e9f

import os, time, sys, subprocess

if len(sys.argv) == 2:
    time.sleep(5)
    print 'track end'
    if sys.platform == 'darwin':
        subprocess.Popen(['say', 'hello'])
else:
    print 'main begin'
    subprocess.Popen(['python', os.path.realpath(__file__), '0'], close_fds=True)
    print 'main end'
Джимми Инь
источник
1
В окнах он не отсоединяется, но работает параметр creationflags
SmartManoj,
3
Это решение оставляет подпроцесс как Zombie в Linux.
TitanFighter
@TitanFighter этого можно избежать, установив SIGCHLD SIG_IGN: stackoverflow.com/questions/16807603/…
sailfish009
спасибо @ Джимми, твой ответ - ЕДИНСТВЕННОЕ решение работает для меня.
sailfish009
12

Возможно, вы захотите начать изучение модуля os для разветвления различных потоков (открыв интерактивный сеанс и выполнив справку (os)). Соответствующими функциями являются fork и любые из исполняемых. Чтобы дать вам представление о том, как начать, поместите что-то подобное в функцию, выполняющую разветвление (функция должна принимать список или кортеж 'args' в качестве аргумента, который содержит имя программы и ее параметры; вы также можете захотеть определить stdin, out и err для нового потока):

try:
    pid = os.fork()
except OSError, e:
    ## some debug output
    sys.exit(1)
if pid == 0:
    ## eventually use os.putenv(..) to set environment variables
    ## os.execv strips of args[0] for the arguments
    os.execv(args[0], args)
Джеральд Сенаркленс де Гранси
источник
2
os.fork()действительно полезен, но имеет существенный недостаток - он доступен только в * nix.
Эван Фосмарк
Единственная проблема с os.fork заключается в том, что он специфичен для win32.
JKP
Подробнее об этом подходе: создание демона по пути Python
Амир Али Акбари
Вы также можете достичь аналогичных эффектов с помощью threading: stackoverflow.com/a/53751896/895245 Я думаю, что это может работать на Windows.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
9

И захватывать вывод и работать на фоне с threading

Как уже упоминалось в этом ответе , если вы фиксируете вывод с помощью, stdout=а затем пытаетесь read(), то процесс блокируется.

Однако есть случаи, когда вам это нужно. Например, я хотел запустить два процесса, которые общаются через порт между ними , и сохранить их стандартный вывод в файл журнала и стандартный вывод.

threadingМодуль позволяет нам сделать это.

Во-первых, посмотрите, как выполнить часть перенаправления вывода в этом вопросе: Python Popen: одновременная запись в стандартный вывод и файл журнала

Затем:

main.py

#!/usr/bin/env python3

import os
import subprocess
import sys
import threading

def output_reader(proc, file):
    while True:
        byte = proc.stdout.read(1)
        if byte:
            sys.stdout.buffer.write(byte)
            sys.stdout.flush()
            file.buffer.write(byte)
        else:
            break

with subprocess.Popen(['./sleep.py', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc1, \
     subprocess.Popen(['./sleep.py', '10'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc2, \
     open('log1.log', 'w') as file1, \
     open('log2.log', 'w') as file2:
    t1 = threading.Thread(target=output_reader, args=(proc1, file1))
    t2 = threading.Thread(target=output_reader, args=(proc2, file2))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

sleep.py

#!/usr/bin/env python3

import sys
import time

for i in range(4):
    print(i + int(sys.argv[1]))
    sys.stdout.flush()
    time.sleep(0.5)

После запуска:

./main.py

stdout обновляется каждые 0,5 секунды для каждых двух строк:

0
10
1
11
2
12
3
13

и каждый файл журнала содержит соответствующий журнал для данного процесса.

Вдохновленный: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

Протестировано на Ubuntu 18.04, Python 3.6.7.

Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник