如何从Python异步运行外部命令?

时间:2009-03-11 22:01:43

标签: python asynchronous subprocess scheduler

我需要从Python脚本异步运行shell命令。这意味着我希望我的Python脚本在外部命令发生时继续运行并执行它需要做的任何事情。

我读过这篇文章:

  

Calling an external command in Python

然后我去做了一些测试,看起来os.system()将完成工作,前提是我在命令末尾使用&,这样我就不用等了它回来了。我想知道的是,这是否是实现这一目标的正确方法?我试过了commands.call()但它对我不起作用,因为它会阻止外部命令。

如果使用os.system()这是可取的,或者我应该尝试其他路线,请告诉我。

10 个答案:

答案 0 :(得分:112)

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上发送数据,并等待它终止。

答案 1 :(得分:39)

如果要并行运行多个进程,然后在产生结果时处理它们,可以使用如下所示的轮询:

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并且结果运行时间最长,则其他正在运行的进程将当你可以处理他们的结果时,他们一直坐在那里闲着。

答案 2 :(得分:9)

我想知道的是,[os.system()]是否是完成此类事情的正确方法?

没有。 os.system()不是正确的方法。这就是每个人都说使用subprocess的原因。

有关详情,请参阅http://docs.python.org/library/os.html#os.system

  

子进程模块提供更多功能   强大的产卵设施   处理和检索他们的   结果;使用该模块是   比使用这个功能更好。使用   子进程模块。校验   特别是取代旧的   子进程模块的功能   部分。

答案 3 :(得分:7)

我在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

答案 4 :(得分:6)

将pexpect [http://www.noah.org/wiki/Pexpect]与非阻塞读取线一起使用是另一种方法。 Pexpect解决了死锁问题,允许您在后台轻松运行进程,并在您的进程吐出预定义字符串时提供简单的回调方法,并且通常可以更轻松地与进程交互。

答案 5 :(得分: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()

答案 6 :(得分:2)

考虑到&#34;我不必等待它返回&#34;,最简单的解决方案之一就是:

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

但是......从我读到的这不是&#34;完成这样的事情的正确方法&#34;由于subprocess.CREATE_NEW_CONSOLE标志创建的安全风险。

这里发生的关键事情是使用subprocess.CREATE_NEW_CONSOLE创建新的控制台和.pid(返回进程ID,以便以后可以检查程序),以便不等待计划完成工作。

答案 7 :(得分:2)

“ {等待命令异步终止”下的Python 3 Subprocess Examples涵盖了这一点:

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 answer,但我认为Terrels的回答似乎使事情变得过于复杂。

有关更多信息,请参见asyncio.create_subprocess_exec

答案 8 :(得分: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)

答案 9 :(得分:0)

可接受的答案很旧。

我在这里找到了一个更好的现代答案:

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

进行了一些更改:

  1. 使其在Windows上运行
  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运行多个子进程并输出输出的方法。