Python에서 비동기 적으로 외부 명령을 실행하려면 어떻게해야합니까?


120

Python 스크립트에서 비동기 적으로 셸 명령을 실행해야합니다. 이것은 외부 명령이 꺼지고 필요한 모든 작업을 수행하는 동안 Python 스크립트가 계속 실행되기를 원한다는 것을 의미합니다.

이 게시물을 읽었습니다.

Python에서 외부 명령 호출

그런 다음 나가서 몇 가지 테스트 os.system()를 수행 &했으며 명령이 끝날 때까지 기다릴 필요가 없도록 명령 끝에서 사용하는 경우 작업을 수행 할 것 같습니다 . 내가 궁금한 것은 이것이 그러한 일을 수행하는 적절한 방법인지입니다. 시도 commands.call()했지만 외부 명령을 차단하기 때문에 작동하지 않습니다.

os.system()이것을 위해 사용 하는 것이 바람직하거나 다른 경로를 시도 해야하는지 알려주십시오 .

답변:


135

subprocess.Popen 은 정확히 원하는 작업을 수행합니다.

from subprocess import Popen
p = Popen(['watch', 'ls']) # something long running
# ... do other stuff while subprocess is running
p.terminate()

(댓글에서 답변을 완성하려면 편집)

Popen 인스턴스 poll()는 여전히 실행 중인지 확인하고 communicate()stdin에서 데이터를 보내고 종료 될 때까지 기다릴 수있는 등 다양한 작업을 수행 할 수 있습니다 .


4
또한 poll ()을 사용하여 자식 프로세스가 종료되었는지 확인하거나 wait ()를 사용하여 종료 될 때까지 기다릴 수 있습니다.
Adam Rosenfield

Adam은 매우 사실입니다. 비록 in / out 버퍼를 더 잘 처리하고 이러한 버퍼가 차단 될 수있는 상황이 있기 때문에 기다릴 때 통신 ()을 사용하는 것이 더 나을 수 있습니다.
Ali Afshar

Adam : docs say "Warning 하위 프로세스가 stdout 또는 stderr 파이프에 충분한 출력을 생성하여 OS 파이프 버퍼가 더 많은 데이터를 수신 할 때까지 대기하는 것을 차단하면 교착 상태가됩니다.이를 방지하려면 communication ()을 사용하십시오."
Ali Afshar

14
하지만 communication () 및 wait ()는 작업을 차단하고 있습니다. OP가 사용하는지 묻는 것처럼 명령을 병렬화하지 않을 것입니다.
cdleary

1
Cdleary는 절대적으로 정확합니다. 통신하고 블록을 기다린다는 것을 언급해야합니다. 따라서 작업이 종료되기를 기다리는 동안에 만 수행하십시오. (당신이 정말로 무엇을해야 어떤 잘 행동한다)
알리 Afshar에게

48

여러 프로세스를 병렬로 실행하고 결과가 나올 때 처리하려면 다음과 같이 폴링을 사용할 수 있습니다.

from subprocess import Popen, PIPE
import time

running_procs = [
    Popen(['/usr/bin/my_cmd', '-i %s' % path], stdout=PIPE, stderr=PIPE)
    for path in '/tmp/file0 /tmp/file1 /tmp/file2'.split()]

while running_procs:
    for proc in running_procs:
        retcode = proc.poll()
        if retcode is not None: # Process finished.
            running_procs.remove(proc)
            break
        else: # No process is done, wait a bit and check again.
            time.sleep(.1)
            continue

    # Here, `proc` has finished with return code `retcode`
    if retcode != 0:
        """Error handling."""
    handle_results(proc.stdout)

제어 흐름은 약간 복잡합니다. 제가 그것을 작게 만들려고하기 때문입니다. 취향에 맞게 리팩토링 할 수 있습니다. :-)

이것은 초기 완료 요청을 먼저 처리하는 이점이 있습니다. communicate첫 번째 실행중인 프로세스 를 호출 하고 가장 오래 실행되는 것으로 판명되면 다른 실행중인 프로세스는 결과를 처리 할 수있을 때 유휴 상태에있게됩니다.


3
@Tino 바쁜 대기를 정의하는 방법에 따라 다릅니다. 바쁜 대기와 폴링의 차이점
Piotr Dobrogost

1
하나가 아닌 일련의 프로세스를 폴링하는 방법이 있습니까?
Piotr Dobrogost

1
참고 : 프로세스가 충분한 출력을 생성하면 중단 될 수 있습니다. PIPE를 사용하는 경우 stdout을 동시에 사용해야합니다 (하위 프로세스 문서에 경고가 너무 많지만 충분하지 않음).
jfs

@PiotrDobrogost : 당신은 사용할 수 os.waitpid있는지 여부를 확인 할 수있는 직접 어떤 자식 프로세스의 상태가 변경되었습니다.
jfs

5
['/usr/bin/my_cmd', '-i', path]대신 사용['/usr/bin/my_cmd', '-i %s' % path]
jfs

11

내가 궁금한 것은이 [os.system ()]이 그러한 일을 수행하는 적절한 방법인지 여부입니다.

아니요 os.system(). 올바른 방법이 아닙니다. 그래서 모든 사람들이 subprocess.

자세한 내용은 http://docs.python.org/library/os.html#os.system을 참조하십시오.

하위 프로세스 모듈은 새 프로세스를 생성하고 결과를 검색하기위한보다 강력한 기능을 제공합니다. 이 기능을 사용하는 것보다 해당 모듈을 사용하는 것이 좋습니다. 하위 프로세스 모듈을 사용하십시오. 특히 이전 기능을 하위 프로세스 모듈로 교체 섹션을 확인하십시오.


8

나는 프로세스의 출력을 잘 처리 하는 asyncproc 모듈로 좋은 성공을 거두었습니다 . 예를 들면 :

import os
from asynproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll is not None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

이것은 github의 어디에 있습니까?
Nick

그것은 gpl 라이센스이므로 여러 번 거기에 있다고 확신합니다. 여기 하나가 있습니다 : github.com/albertz/helpers/blob/master/asyncproc.py
Noah

python3에서 작동하도록 몇 가지 수정 사항이 포함 된 요점을 추가했습니다. (대부분 str을 바이트로 대체합니다). 참조 gist.github.com/grandemk/cbc528719e46b5a0ffbd07e3054aab83
과자

1
또한 루프를 종료 한 후 출력을 한 번 더 읽어야합니다. 그렇지 않으면 출력의 일부가 손실됩니다.
Tic

7

non-blocking readlines와 함께 pexpect 를 사용 하는 것도이를위한 또 다른 방법입니다. Pexpect는 교착 상태 문제를 해결하고, 프로세스를 백그라운드에서 쉽게 실행할 수 있으며, 프로세스가 사전 정의 된 문자열을 뱉어 낼 때 콜백을 쉽게 할 수있는 방법을 제공하고 일반적으로 프로세스와의 상호 작용을 훨씬 쉽게 만듭니다.


4

"돌아올 때까지 기다릴 필요가 없습니다"를 고려할 때 가장 쉬운 해결책 중 하나는 다음과 같습니다.

subprocess.Popen( \
    [path_to_executable, arg1, arg2, ... argN],
    creationflags = subprocess.CREATE_NEW_CONSOLE,
).pid

하지만 ... 제가 읽은 바에 따르면 이것은 "이러한 일을 수행하는 적절한 방법"이 아닙니다. subprocess.CREATE_NEW_CONSOLE 플래그에 .

여기서 일어나는 핵심은를 사용 subprocess.CREATE_NEW_CONSOLE하여 새 콘솔을 만들고 .pid(원할 경우 나중에 프로그램을 확인할 수 있도록 프로세스 ID를 반환) 프로그램이 작업을 완료 할 때까지 기다리지 않도록하는 것입니다.


3

Python에서 s3270 스크립팅 소프트웨어를 사용하여 3270 터미널에 연결하는 데 동일한 문제가 있습니다. 이제 여기에서 찾은 Process의 하위 클래스로 문제를 해결하고 있습니다.

http://code.activestate.com/recipes/440554/

다음은 파일에서 가져온 샘플입니다.

def recv_some(p, t=.1, e=1, tr=5, stderr=0):
    if tr < 1:
        tr = 1
    x = time.time()+t
    y = []
    r = ''
    pr = p.recv
    if stderr:
        pr = p.recv_err
    while time.time() < x or r:
        r = pr()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            y.append(r)
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return ''.join(y)

def send_all(p, data):
    while len(data):
        sent = p.send(data)
        if sent is None:
            raise Exception(message)
        data = buffer(data, sent)

if __name__ == '__main__':
    if sys.platform == 'win32':
        shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n')
    else:
        shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n')

    a = Popen(shell, stdin=PIPE, stdout=PIPE)
    print recv_some(a),
    for cmd in commands:
        send_all(a, cmd + tail)
        print recv_some(a),
    send_all(a, 'exit' + tail)
    print recv_some(a, e=0)
    a.wait()

3

받아 들여지는 대답은 매우 오래되었습니다.

여기에서 더 나은 현대적인 답을 찾았습니다.

https://kevinmccarthy.org/2016/07/25/streaming-subprocess-stdin-and-stdout-with-asyncio-in-python/

몇 가지 사항을 변경했습니다.

  1. 창문에서 작동하게
  2. 여러 명령으로 작동하도록 만들기
import sys
import asyncio

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())


async def _read_stream(stream, cb):
    while True:
        line = await stream.readline()
        if line:
            cb(line)
        else:
            break


async def _stream_subprocess(cmd, stdout_cb, stderr_cb):
    try:
        process = await asyncio.create_subprocess_exec(
            *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
        )

        await asyncio.wait(
            [
                _read_stream(process.stdout, stdout_cb),
                _read_stream(process.stderr, stderr_cb),
            ]
        )
        rc = await process.wait()
        return process.pid, rc
    except OSError as e:
        # the program will hang if we let any exception propagate
        return e


def execute(*aws):
    """ run the given coroutines in an asyncio loop
    returns a list containing the values returned from each coroutine.
    """
    loop = asyncio.get_event_loop()
    rc = loop.run_until_complete(asyncio.gather(*aws))
    loop.close()
    return rc


def printer(label):
    def pr(*args, **kw):
        print(label, *args, **kw)

    return pr


def name_it(start=0, template="s{}"):
    """a simple generator for task names
    """
    while True:
        yield template.format(start)
        start += 1


def runners(cmds):
    """
    cmds is a list of commands to excecute as subprocesses
    each item is a list appropriate for use by subprocess.call
    """
    next_name = name_it().__next__
    for cmd in cmds:
        name = next_name()
        out = printer(f"{name}.stdout")
        err = printer(f"{name}.stderr")
        yield _stream_subprocess(cmd, out, err)


if __name__ == "__main__":
    cmds = (
        [
            "sh",
            "-c",
            """echo "$SHELL"-stdout && sleep 1 && echo stderr 1>&2 && sleep 1 && echo done""",
        ],
        [
            "bash",
            "-c",
            "echo 'hello, Dave.' && sleep 1 && echo dave_err 1>&2 && sleep 1 && echo done",
        ],
        [sys.executable, "-c", 'print("hello from python");import sys;sys.exit(2)'],
    )

    print(execute(*runners(cmds)))

예제 명령이 시스템에서 완벽하게 작동 할 가능성은 낮으며 이상한 오류를 처리하지 않지만이 코드는 asyncio를 사용하여 여러 하위 프로세스를 실행하고 출력을 스트리밍하는 한 가지 방법을 보여줍니다.


우분투 WSL 및 기본 알파인 리눅스에서 실행되는 윈도우와 3.7.3 CPython의 실행 CPython과 3.7.4에서이 테스트
Terrel 셤 웨이


1

여기에 몇 가지 답변이 있지만 그중 어느 것도 아래 요구 사항을 충족하지 못했습니다.

  1. 명령이 완료 될 때까지 기다리거나 하위 프로세스 출력으로 터미널을 오염시키고 싶지 않습니다.

  2. 리디렉션으로 bash 스크립트를 실행하고 싶습니다.

  3. bash 스크립트 내에서 파이핑을 지원하고 싶습니다 (예 :) find ... | tar ....

위의 요구 사항을 충족하는 유일한 조합은 다음과 같습니다.

subprocess.Popen(['./my_script.sh "arg1" > "redirect/path/to"'],
                 stdout=subprocess.PIPE, 
                 stderr=subprocess.PIPE,
                 shell=True)

0

이는 "명령이 비동기 적으로 종료 될 때까지 기다 립니다 "의 Python 3 하위 프로세스 예제 에서 다룹니다 .

import asyncio

proc = await asyncio.create_subprocess_exec(
    'ls','-lha',
    stdout=asyncio.subprocess.PIPE,
    stderr=asyncio.subprocess.PIPE)

# do something else while ls is working

# if proc takes very long to complete, the CPUs are free to use cycles for 
# other processes
stdout, stderr = await proc.communicate()

프로세스 await asyncio.create_subprocess_exec(...)가 완료 되는 즉시 실행이 시작 됩니다. 호출 await proc.communicate()할 때까지 완료되지 않은 경우 출력 상태를 제공하기 위해 대기합니다. 완료되면 proc.communicate()즉시 반환됩니다.

여기의 요지는 Terrels 답변 과 유사합니다. 하지만 Terrels 답변이 상황을 지나치게 복잡하게 만드는 것으로 보입니다.

자세한 내용은를 참조하십시오 asyncio.create_subprocess_exec.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.