programing

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

powerit 2023. 6. 27. 22:34
반응형

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

Python 스크립트에서 셸 명령을 비동기식으로 실행해야 합니다.즉, 외부 명령이 실행되고 필요한 작업을 수행하는 동안 Python 스크립트가 계속 실행되기를 원합니다.

이 게시물을 읽었습니다.

Python에서 외부 명령 호출

그리고 나서 제가 가서 몇 가지 테스트를 해봤는데, 그것은 마치os.system()내가 사용하는 일을 할 것입니다.&명령이 반환될 때까지 기다릴 필요가 없습니다.내가 궁금한 것은 이것이 그런 일을 하는 적절한 방법인가 하는 것입니다.나는 노력했다.commands.call()외부 명령을 차단하기 때문에 저에게는 작동하지 않을 것입니다.

사용할 경우 알려주시기 바랍니다.os.system()이것이 바람직하거나 다른 경로를 시도해야 할 경우.

subprocess.Popen 정확히 당신이 원하는 것을 합니다.

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

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

Popen 인스턴스는 다른 여러 가지 작업을 수행할 수 있습니다. 예를 들어 Popen 인스턴스가 아직 실행 중인지 확인하고 stdin에서 데이터를 전송하고 종료될 때까지 기다릴 수 있습니다.

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

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처음 실행 중인 프로세스에서 가장 오래 실행되는 것으로 확인되며, 다른 실행 중인 프로세스는 결과를 처리할 수 있었을 때 유휴 상태로 남아 있게 됩니다.

이 내용은 "비동기적으로 종료되는 명령을 기다립니다"의 Python 3 하위 프로세스 예제에서 다룹니다.다음을 사용하여 이 코드 실행IPython또는python -m asyncio:

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()출력 상태를 제공하기 위해 대기합니다. 만약그끝면다났이것,면,▁if▁it다끝▁finished,proc.communicate()즉시 돌아올 것입니다

여기서 요지는 테렐의 대답과 비슷하지만 나는 테렐의 대답이 일을 너무 복잡하게 만드는 것처럼 보인다고 생각합니다.

자세한 내용은 을 참조하십시오.

편집:

당신은 통과할 필요 없이 다른 파이썬 코드와 마찬가지로 위의 코드를 실행할 수 있습니다.-m asyncio를 사용하여asyncio.run()기능.

import asyncio

def main():
    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()
 
asyncio.run(main())

자세한 내용은 을 참조하십시오.

제가 궁금한 것은 이 [os.system()]이(가) 그런 일을 할 수 있는 적절한 방법인가요?

아니요.os.system()그것은 적절한 방법이 아닙니다.그래서 모든 사람들이 사용하라고 하는 것입니다.subprocess.

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

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

승인된 답변은 매우 오래되었습니다.

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

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)))

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

저는 프로세스의 출력물을 잘 처리하는 비동기 프로세스 모듈로 좋은 성공을 거두었습니다.예:

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

비차단 리드라인과 함께 pexpect를 사용하는 것도 방법입니다.Pexpect는 교착 상태 문제를 해결하고 백그라운드에서 프로세스를 쉽게 실행할 수 있도록 하며 프로세스가 미리 정의된 문자열을 뱉을 때 콜백을 수행하는 쉬운 방법을 제공하며 일반적으로 프로세스와 상호 작용을 훨씬 쉽게 합니다.

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

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

하지만... 제가 읽은 바로는 이것은 "그런 일을 할 수 있는 적절한 방법"이 아닙니다. 왜냐하면 보안상의 위험 때문입니다.subprocess.CREATE_NEW_CONSOLE깃발

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

파이썬에서 s3270 스크립트 소프트웨어를 사용하여 3270 터미널에 연결하려고 시도하는 것과 동일한 문제가 있습니다.이제 여기서 찾은 프로세스의 하위 클래스로 문제를 해결하고 있습니다.

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()

여기에는 몇 가지 답변이 있지만 아래의 요구 사항을 충족하는 답변은 없습니다.

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

  2. 리디렉션을 사용하여 bash 스크립트를 실행합니다.

  3. 스크립트하고 싶습니다(예: bash 예지원다니합를프스크이파립내트).find ... | tar ...).

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

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

언급URL : https://stackoverflow.com/questions/636561/how-can-i-run-an-external-command-asynchronously-from-python

반응형