启动多个Shell进程

3 投票
3 回答
3194 浏览
提问于 2025-04-16 04:26

我想用Python同时在多个独立的终端窗口中启动一个命令。请问有什么好的方法吗?现在我正在尝试使用subprocess模块中的popen,这个方法可以用来执行一个命令,但对多个命令就不行了。

提前谢谢你们。

编辑:

这是我现在的做法:

from subprocess import*

Popen('ant -Dport='+str(5555)+ ' -Dhost='+GetIP()+ ' -DhubURL=http://192.168.1.113:4444 -Denvironment=*firefox launch-remote-control $HOME/selenium-grid-1.0.8', shell=True)

对我来说,问题在于这个方法会在终端中启动一个Java进程,而我希望这个进程能够一直运行下去。另外,我还想在多个不同的进程中多次运行类似的命令。

3 个回答

0

这里有一个简单的阻塞队列的版本。你可以用 collections.deque 之类的东西让它变得更好,或者用 Twisted 的 deferreds 之类的更高级的东西。这个版本有一些不太好的地方:

  • 会阻塞
  • 终止信号可能不会传递下去

根据自己的需要调整吧!

import logging
basicConfig = dict(level=logging.INFO, format='%(process)s %(asctime)s %(lineno)s %(levelname)s %(name)s %(message)s')
logging.basicConfig(**basicConfig)
logger = logging.getLogger({"__main__":None}.get(__name__, __name__))

import subprocess

def wait_all(list_of_Popens,sleep_time):
    """ blocking wait for all jobs to return.

    Args:
        list_of_Popens. list of possibly opened jobs

    Returns:
        list_of_Popens. list of possibly opened jobs

    Side Effect:
        block until all jobs complete.
    """
    jobs = list_of_Popens
    while None in [j.returncode for j in jobs]:
        for j in jobs:  j.poll()
        logger.info("not all jobs complete, sleeping for %i", last_sleep)
        time.sleep(sleep_time)  

    return jobs


jobs = [subprocess.Popen('sleep 1'.split()) for x in range(10)]
jobs = wait_all(jobs)
1

我能想到的简单方法是让Python使用 Popen 来启动一个类似于下面的脚本:

gnome-terminal --window -e 'ant -Dport=5555 -Dhost=$IP1 -DhubURL=http://192.168.1.113:4444 -Denvironment=*firefox launch-remote-control $HOME/selenium-grid-1.0.8' &
disown
gnome-terminal --window -e 'ant -Dport=5555 -Dhost=$IP2 -DhubURL=http://192.168.1.113:4444 -Denvironment=*firefox launch-remote-control $HOME/selenium-grid-1.0.8' &
disown
# etc. ...

其实有一种完全用Python实现的方法,但那样写起来很麻烦,只能在类Unix操作系统上运行,而且我没时间把代码写出来。基本上,subprocess.Popen 不支持这种方式,因为它假设你要么等着子进程完成,要么和子进程互动,或者监控子进程。它不支持“就启动它,别再管我”的情况。

在类Unix操作系统中,通常是这样做的:

  • 使用 fork 来创建一个子进程
  • 让这个子进程再 fork 一个自己的子进程
  • 让这个孙子进程把输入输出重定向到 /dev/null,然后用某个 exec 函数来启动你真正想要启动的进程(这部分可能可以用 Popen 来实现)
  • 子进程退出。
  • 现在,祖父进程和孙子进程之间没有联系,所以如果孙子进程结束了,你不会收到 SIGCHLD 信号,而如果祖父进程结束了,也不会把所有的孙子进程都杀掉。

我可能在细节上有些不准确,但大概就是这个意思。在 bash 中,后台运行(&)和 disown 的操作应该能实现同样的效果。

1

这个过程只要在运行,就应该保持打开。如果你想同时启动多个这样的过程,只需把它放在一个线程里。

这段代码没有经过测试,但你应该能理解大概意思:


class PopenThread(threading.Thread):

    def __init__(self, port):
        threading.Thread.__init__(self)
        self.port=port

    def run(self):
        Popen('ant -Dport='+str(self.port)+ ' -Dhost='+GetIP()+ 
                ' -DhubURL=http://192.168.1.113:4444' 
                ' -Denvironment=*firefox launch-remote-control'
                ' $HOME/selenium-grid-1.0.8', shell=True)

if '__main__'==__name__:
    PopenThread(5555).start()
    PopenThread(5556).start()
    PopenThread(5557).start()

补充说明:下面提到的双重分叉方法:https://stackoverflow.com/a/3765162/450517,是启动守护进程的正确方式,也就是一个不会通过标准输入输出进行通信的长时间运行的过程。

撰写回答