【问题标题】:Running pexpect subprocesses in background在后台运行 pexpect 子进程
【发布时间】:2018-08-23 10:18:39
【问题描述】:

我正在运行以下代码

try:
    child = pexpect.spawn(
        ('some command --path {0}  somethingmore --args {1}').format(
            <iterator-output>,something),
        timeout=300)
    child.logfile = open(file_name,'w')
    child.expect('x*')
    child.sendline(something)
    child.expect('E*')
    child.sendline(something))
   #child.read()
    child.interact()
    time.sleep(15)
    print child.status
except Exception as e:
    print "Exception in child process"
    print str(e)

现在,pexpect 中的命令通过从循环中获取输入之一来创建子进程,现在每次启动子进程时,我都会尝试通过 child.read 捕获日志,在这种情况下,它会等待该子进程在再次进入循环之前完成,我如何让它继续在后台运行它(我得到我动态输入的命令输入/输出的日志,但不是此后运行的进程的日志,除非我使用读取或交互?我使用了这个How do I make a command to run in background using pexpect.spawn?,但它使用了interact,它再次等待该子进程完成..因为循环将被迭代几乎超过100次,我不能等待一个完成,然后再移动到另一个,因为pexpect中的命令是一个 AWS lambda 调用,我只需要确保命令被触发,但我无法在不等待它完成的情况下捕获该调用的过程输出......请告诉我您的建议

【问题讨论】:

    标签: python amazon-web-services lambda subprocess pexpect


    【解决方案1】:

    如果您想在后台运行一个进程,但同时与它进行交互,最简单的解决方案是启动一个线程与该进程进行交互。*


    在您的情况下,听起来您正在运行数百个进程,因此您希望并行运行其中一些,但可能不是同时运行所有进程?如果是这样,您应该使用线程池或执行程序。例如,使用 stdlib 中的 concurrent.futures(如果您的 Python 太旧,则使用 pip install 后端口):

    def run_command(path, arg):
        try:
            child = pexpect.spawn(('some command --path {0}  somethingmore --args {1}').format(path, arg), timeout=300)
            child.logfile = open(file_name,'w')
            child.expect('x*')
            child.sendline(something)
            child.expect('E*')
            child.sendline(something))
            # child.read()
            child.interact()
            time.sleep(15)
            print child.status
        except Exception as e:
            print "Exception in child process"
            print str(e)
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as x:
        fs = []
        for path, arg in some_iterable:
            fs.append(x.submit(run_command, path, arg))
        concurrent.futures.wait(fs)
    

    如果您需要从线程代码中返回一个值(或引发异常),您可能需要在as_completed(fs) 上进行循环,而不仅仅是简单的wait。但是在这里,您似乎只是在 print 把东西拿出来然后忘记了。

    如果path, arg 真的直接来自这样的可迭代对象,那么使用x.map(run_command, some_iterable) 通常会更简单。

    所有这些(以及其他选项)在模块文档中都有很好的解释。


    另见pexpectFAQcommon problems。我认为在当前版本中没有任何问题会影响您(我们总是在单个线程池任务中生成孩子并完全与它进行交互),但我隐约记得曾经有一个额外的问题过去(与信号有关?)。


    * 我认为asyncio 会是一个更好的解决方案,除了据我所知,以非阻塞方式分叉或重新实现pexpect 的尝试都不足以真正使用......

    【讨论】:

    • 感谢您的及时回复,但它仍然没有在后台运行,它等待一个完成,然后再进行另一个。另外,关于你的问题“在你的情况下,听起来你正在运行数百个进程,所以你想并行运行其中一些,但可能不是一次全部运行?” ,并行运行它们将是解决方案,但从那时起会有大量进程,我想要实现的是,一旦它启动子进程,它将交互式输出记录到日志文件而不是在那里等待......
    • @dheerajtripathi 首先,使用Executor 代码,它应该同时运行其中的8 个。它将等待 8 个中的一个完成,然后再开始另一个;这是确保它一次只运行 8 个而不是同时运行所有这些的唯一方法。如果您真的想同时运行所有这些,您可以删除Executor 并为每个生成一个线程。如果你想要,但不明白怎么做,我可以为它写一些示例代码。
    • @dheerajtripathi 其次,我不确定我是否理解您评论的最后一部分。您是说您最初需要与进程交互,但之后您可以将其转储到后台,忽略它的作用,然后启动下一个进程?
    • “你是说你最初需要与进程交互,但之后你可以将它转储到后台,忽略它的作用,然后启动下一个进程?”正在考虑这个,但我意识到它可能会变得丑陋?
    • "关于评论首先,使用 Executor",8 个 executor 一次有一点混乱,因为从未使用过 executor,代码一次运行 8 个这些线程,但子进程是一个,所以不是正在使用1个线程吗,现在在这种情况下,如果我仍然需要等待交互过程完成,然后控件才能旋转新的子进程,那么它是否仍然是相同的旧方式w /o 执行者?真的理解执行者如何在这里帮助我吗?它会减少我的执行时间吗(因为它似乎直到完成一个才继续进行,感谢您的帮助
    【解决方案2】:

    如果您实际上并不想与许多并行进程交互,而是想与每个进程短暂交互,那么在它运行时忽略它并继续与下一个交互...

    # Do everything up to the final `interact`. After that, the child
    # won't be writing to us anymore, but it will still be running for
    # many seconds. So, return the child object so we can deal with it
    # later, after we've started up all the other children.
    def start_command(path, arg):
        try:
            child = pexpect.spawn(('some command --path {0}  somethingmore --args {1}').format(path, arg), timeout=300)
            child.logfile = open(file_name,'w')
            child.expect('x*')
            child.sendline(something)
            child.expect('E*')
            child.sendline(something))
            # child.read()
            child.interact()
            return child
        except Exception as e:
            print "Exception in child process"
            print str(e)
    
    # First, start up all the children and do the initial interaction
    # with each one.
    children = []
    for path, args in some_iterable:
        children.append(start_command(path, args))
    
    # Now we just need to wait until they're all done. This will get
    # them in as-launched order, rather than as-completed, but that
    # seems like it should be fine for your use case.
    for child in children:
        try:
            child.wait()
            print child.status
        except Exception as e:
            print "Exception in child process"
            print str(e)
    

    一些事情:

    来自代码 cmets 的通知,我假设孩子在初始交互后没有向我们写任何东西(并等待我们阅读它)。如果这不是真的,事情就有点复杂了。

    如果您不仅想这样做,而且一次启动 8 个孩子,甚至一次启动所有孩子,您可以(如我的另一个答案所示)使用执行器或只是一堆线程最初的start_command 调用,并让这些任务/线程稍后将子对象返回为waited。例如,对于Executor 版本,每个future 的result() 将是一个pexpect 子进程。但是,在这种情况下,您肯定需要阅读有关线程的 pexpect 文档 — 对于某些版本的 linux,在线程之间传递子进程对象可能会破坏对象。

    最后,由于您现在看到的内容比原始版本更乱,您可能需要更改您的 print 语句以显示您正在为哪个孩子打印(这也可能意味着更改 @ 987654328@ 从子列表到 (child, path, arg) 元组列表等)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-10
      • 2021-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-19
      相关资源
      最近更新 更多