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

2025-01-22 08:45:00
admin
原创
97
摘要:问题描述:我需要从 Python 脚本异步运行 shell 命令。我的意思是,我希望我的 Python 脚本在外部命令执行时继续运行并执行它需要执行的任何操作。我读过这篇文章:在 Python 中调用外部命令然后我去进行了一些测试,看起来只要我在命令末尾os.system()使用,它就可以完成工作,这样我就不...

问题描述:

我需要从 Python 脚本异步运行 shell 命令。我的意思是,我希望我的 Python 脚本在外部命令执行时继续运行并执行它需要执行的任何操作。

我读过这篇文章:

在 Python 中调用外部命令

然后我去进行了一些测试,看起来只要我在命令末尾os.system()使用,它就可以完成工作,这样我就不必等待它返回。我想知道这是完成这样一件事的正确方法吗?我试过了,但它对我来说不起作用,因为它会阻止外部命令。&`commands.call()`

请让我知道是否os.system()建议使用此方法或者我是否应该尝试其他方法。


解决方案 1:

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

解决方案 2:

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(),它将等待以便为您提供输出状态。如果进程已完成,proc.communicate()将立即返回。

这里的要点与Terrels 的回答类似,但我认为 Terrels 的回答似乎使事情过于复杂。

请参阅asyncio.create_subprocess_exec以了解更多信息。

编辑

您可以像任何其他 Python 代码一样运行上述代码,而无需-m asyncio通过使用asyncio.run()函数。

import asyncio

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

请参阅asyncio以了解更多信息

解决方案 3:

如果您想要并行运行多个进程,然后在它们产生结果时处理它们,那么您可以像下面这样使用轮询:

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第一个正在运行的进程,而该进程运行时间最长,那么当您本可以处理其他进程的结果时,它们将一直处于空闲状态。

解决方案 4:

我想知道的是这个 [os.system()] 是否是完成这样的事情的正确方法?

不。 os.system()不是正确的方法。 这就是为什么每个人都说要使用subprocess

有关更多信息,请阅读http://docs.python.org/library/os.html#os.system

subprocess 模块提供了更强大的功能,用于生成新进程并检索其结果;使用该模块比使用此功能更可取。请使用 subprocess 模块。请特别检查使用 subprocess 模块替换旧功能部分。

解决方案 5:

接受的答案非常古老。

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

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 运行多个子进程并流式传输输出的方法。

解决方案 6:

我使用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

解决方案 7:

使用pexpect和非阻塞 readlines 是实现此目的的另一种方法。Pexpect 解决了死锁问题,允许您轻松地在后台运行进程,并在您的进程吐出预定义字符串时提供简单的回调方法,并且通常使与进程的交互变得更加容易。

解决方案 8:

考虑到“我不必等待它返回”,最简单的解决方案之一是:

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

但是...从我所读的内容来看,这不是“完成这样的事情的正确方法”,因为subprocess.CREATE_NEW_CONSOLE标志会产生安全风险。

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

解决方案 9:

我在尝试使用 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'), '
')
    else:
        shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '
')

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

解决方案 10:

这里有几个答案,但没有一个满足我的以下要求:

  1. 我不想等待命令完成或用子进程输出污染我的终端。

  2. 我想运行带有重定向的 bash 脚本。

  3. 我想在我的 bash 脚本中支持管道(例如find ... | tar ...)。

唯一满足上述要求的组合是:

subprocess.Popen(['./my_script.sh "arg1" > "redirect/path/to"'],
                 stdout=subprocess.PIPE, 
                 stderr=subprocess.PIPE,
                 shell=True)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2796  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1710  
  PLM系统在企业项目管理中扮演着至关重要的角色,尤其是在项目采购管理方面,能够通过一系列策略提升采购效率、降低成本并保障质量。通过深入解析相关策略,企业可以更好地利用PLM系统优化采购流程,实现项目的顺利推进与整体目标的达成。需求精准定义策略在项目采购中,明确需求是首要任务。PLM系统可助力企业精准定义采购需求。首先,...
plm是什么意思   8  
  在企业的运营过程中,跨部门数据共享一直是一个关键且颇具挑战的问题。不同部门之间由于业务差异、系统不兼容等多种因素,常常形成信息孤岛,导致数据无法顺畅流通,影响企业整体的决策效率和协同效果。而 PLM 系统作为一种先进的管理工具,为解决这一难题提供了有效的途径。通过其一系列强大的核心功能,能够打破部门之间的数据壁垒,实现...
plm系统   7  
  PLM(产品生命周期管理)项目涉及产品从概念设计到退役的全流程管理,其复杂性和长期性要求高效的项目进度管理工具。甘特图作为一种直观且实用的项目进度可视化工具,在PLM项目中发挥着关键作用。通过甘特图,项目团队成员能够清晰地了解项目任务的时间安排、进度状态以及各项任务之间的关系,从而更好地协调工作、分配资源,确保项目按计...
plm流程是什么   6  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用