如何使用独立的 stdout、stderr 和 stdin 分叉一个新进程?
Posted
技术标签:
【中文标题】如何使用独立的 stdout、stderr 和 stdin 分叉一个新进程?【英文标题】:How do I fork a new process with independent stdout, stderr, and stdin? 【发布时间】:2015-03-16 12:17:17 【问题描述】:我已经阅读了大部分关于 subprocess 和 os.fork() 的相关问题,包括所有关于双分叉技巧的讨论。但是,这些解决方案似乎都不适用于我的场景。
我想分叉一个新进程并允许父进程(通常)终止,而不会弄乱子进程的标准输入、标准输出和标准错误,也不会杀死子进程。
我的第一次尝试是使用subprocess.Popen()
。
#!/usr/bin/python
from subprocess import call,Popen
Popen("/bin/bash", shell=True)
call("echo Hello > /tmp/FooBar", shell=True)
这会失败,因为一旦父进程退出,子进程就会被杀死。我知道creationflags
,但这是特定于 Windows 的,我在 Linux 上运行。请注意,如果我们只是通过在其末尾添加一个无限循环来保持父进程处于活动状态,则上面的代码可以很好地工作。这是不可取的,因为父母已经完成了它的工作,没有真正的理由让它留下来。
第二次尝试是使用os.fork()
。
#!/usr/bin/python
from subprocess import call
from os import fork
try:
pid = fork()
if pid > 0:
pass
else: # child process will start interactive process
call("/bin/bash", shell=True)
except:
print "Forking failed!"
call("echo Hello > /tmp/FooBar", shell=True)
这里,子进程不再与父进程一起死亡,但在父进程死亡后,子进程不能再读取输入和写入输出。
因此,我想知道如何使用完全独立的 stdout
、stderr
和 stdin
分叉一个新进程。独立性意味着父进程可以(通常)终止,而子进程(无论是 bash 还是 tmux 或任何其他交互式程序)的行为就像父程序没有终止一样。更准确地说,请考虑以下原始程序的变体。
#!/usr/bin/python
from subprocess import call,Popen
Popen("/bin/bash", shell=True)
call("echo Hello > /tmp/FooBar", shell=True)
while True:
pass
上面的代码具有我所寻求的所有行为,但它人为地使 Python 进程保持活力。我正在尝试实现这种确切的行为,Python 进程不存在。
警告:我通过 ssh 运行这些应用程序,因此生成新的 GUI 窗口不是一个可行的解决方案。
期望的行为:
-
我运行 python 代码。
我得到了一个闪亮的新
bash
shell,它的工作方式与我开始使用的 bash shell 完全一样。
文件/tmp/FooBar
已创建。
原始 Python 脚本完成。
我继续使用我闪亮的新 bash shell,ps aux | grep python
的输出不包括我刚刚运行的 python 脚本。
【问题讨论】:
(在那个 Popen 技术中)孩子是守护进程吗?如果不是,那它怎么会在父母去世后立即终止? 不能 >>> Popen("tmux -S /tmp/TestSmux attach-session", shell=True) 在单独的线程中完成?这个线程不能是一个守护进程,否则它不会给出想要的结果。您可以阻止该线程的 RUN 方法完成,直到您的任务完成...这样您的父进程将继续执行其工作,如果您希望在父级基于子级接收到的输入。然后你可以从父母发出信号(阻止孩子)或加入确保父母和孩子都停止。 1.tmux
是一个复杂的例子。如果您需要 tmux
;询问您遇到的具体问题。 2.您的问题是:如何模拟nohup
、disown
或在Python中创建一个合适的守护进程?
@SyedMauzeRehan,我给了你完整的可重复的例子,以及我对行为的观察。它的行为就像一个守护进程,但它是一个吗?我不能说我肯定知道。
@SyedMauzeRehan,对于单独线程的建议,如果我不能单独使用进程,我会尝试这样做。但是,如果您认为它会起作用并且您有时间对其进行测试并确保所有用户交互都正常工作,那么欢迎您写一个答案。
【参考方案1】:
您的第一个示例对 Popen() 进行了额外的不必要调用,因为 call 便利函数只会在 shell 中执行其命令并退出,因此如果您只是运行它就可以工作:
from subprocess import call, Popen
call("echo Hello > /tmp/FooBar", shell=True)
但是,如果您想向 shell 发送一系列命令,那么 shell 需要打开连接到管道的标准输入,这样它就不会与父标准输入混淆(这实际上是您在获取独立stdio的条件):
p = Popen("/bin/bash", shell=True, stdin = subprocess.PIPE)
p.stdin.write("echo Hello > /tmp/FooBar\n")
p.stdin.write("date >> /tmp/FooBar\n")
p.terminate()
如果您还想控制输出,则将 stdout 和 stderr 重定向到 PIPE(即stdout = subprocess.PIPE
、stderr = subprocess.PIPE
),然后根据需要调用 p.stdout.read() 以获取输出。
要让进程在退出 python 后继续运行,可以在命令末尾添加 &
运算符,以便它继续在后台运行,例如:
call("nc -l 2000 > /tmp/nc < &",shell=True)
让一个进程运行,以便 stdin 和 stdout 并且仍然连接的一个可以使用 shell 重定向。为了保持对标准输入的访问,可以使用mkfifo
创建一个命名管道,例如:
call("mkfifo /tmp/pipe",shell=True)
call("tail -f /tmp/pipe > /tmp/out &",shell=True)
要提供输入标准输入,只需将数据发送到管道。例如从外壳:
$ echo 'test' > /tmp/pipe
【讨论】:
这个答案没有实现我在问题中描述的行为。请阅读问题的最后一部分。 我已经更新了我的答案 - 如果你在后台处理进程,那么它会在 python 退出后继续。 这并没有解决为子进程维护交互式stdin
的问题。
我添加了另一个更新来解释如何使用管道来保持对标准输入的访问。
这些是有用的技巧,但它们忽略了主要问题:维护一个与任何其他 shell 完全相同的交互式 shell。【参考方案2】:
我最近遇到了这个问题,我找到了一个可能有帮助的解决方案,
使用creationflags
告诉 Popen 子进程不应继承父进程控制台,因此它拥有自己的 stdout、stdin 和 stderr,就好像它是父进程一样。
subprocess.Popen("/bin/bash", creationflags= subprocess.CREATE_NEW_CONSOLE)
根据文档,Popen 支持 creationflags
关键字参数:
creationflags,如果给定,可以是以下一个或多个标志:
CREATE_NEW_CONSOLE
CREATE_NEW_PROCESS_GROUP
ABOVE_NORMAL_PRIORITY_CLASS
BELOW_NORMAL_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
IDLE_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
REALTIME_PRIORITY_CLASS
CREATE_NO_WINDOW
DETACHED_PROCESS
CREATE_DEFAULT_ERROR_MODE
CREATE_BREAKAWAY_FROM_JOB
你感兴趣的是DETACHED_PROCESS
和CREATE_NEW_CONSOLE
,我用过CREATE_NEW_CONSOLE
,它的作用是产生一个新进程,就好像它是一个父进程一样,我用的是Python3.5 Windows,在 Python 3.7 中添加了 DETACHED_PROCESS
,并记录了它与 CREATE_NEW_CONSOLE
的作用相同。
【讨论】:
以上是关于如何使用独立的 stdout、stderr 和 stdin 分叉一个新进程?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 stdout、stderr 重新路由回 /dev/tty
如何在 Perl 中读取和写入大缓冲区到进程 stdin/stdout/stderr?