示例
我在cli应用程序ngrok中注意到了这种行为。这只是本例的特殊之处,因为它会污染父进程终端。它的核心功能并不重要。
获取可执行文件:
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
unzip ngrok-stable-linux-amd64.zip
# There is `ngrok` executable in this dir now
造成问题的代码:
# ngrok_python.py
# It's in the same dir as the ngrok executable
import pty
import subprocess
import shlex
import time
import sys
pty_master, pty_slave = pty.openpty()
ngrok_cmd = "./ngrok http 80"
# This doesn't happen for others
# ngrok_cmd = "ls -la"
ngrok = subprocess.Popen(shlex.split(ngrok_cmd), stdin=pty_slave, stdout=pty_slave, stderr=pty_slave)
# It also won't pollute the current terminal when redirected to DEVNULL
# ngrok = subprocess.Popen(shlex.split(ngrok_cmd), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print("The subprocess is attached to the pseudo terminal")
print("Its output should not be printed here")
print("Unless I decide to read from pty_master")
print("Checking a few times if the subprocess is done")
for i in range(3):
time.sleep(1)
if ngrok.poll is not None:
print("Subprocess finished")
sys.exit()
print("Don't want to wait any longer.")
# Running the command
python3 ngrok_python.py
预期行为
- 唯一的输出将来自打印语句
subprocess
具有自定义stdout/err/in
。它无法访问主终端- 由于
pty
,如果我想读取子流程中发生的事情,我会从pty_master
中读取
实际行为
- 子流程
./ngrok http 80
的输出消耗终端
奇怪的是,运行注释掉的部分(ngrok_cmd = "ls -la"
或subprocess with subprocess.DEVNULL
(会导致预期的行为。
问题
- 如果为孩子提供的
stdout/err/in
发生了更改,为什么孩子进程知道如何访问家长终端 - 如何在python中克服这一点
我不愿意运行从某个随机站点下载的可执行文件,但我愿意打赌ngrok
会显式打开并写入/dev/tty
,以显示其连接信息。
/dev/tty
设备指的是进程的"控制终端",这是进程从其父进程继承的东西之一。重新分配子代的stdin
、stdout
和stderr
不影响其控制终端。因此,在这种情况下,子级保留与父级相同的控制终端,当子级打开并写入/dev/tty
时,输出直接进入父级的屏幕,而不是通过子级的stdout
或stderr
或您的伪终端。
为了实现你想要的目标,你需要将孩子与父母的控制终端分离,并将伪终端的从端建立为孩子的控制终端。这涉及到对setsid
和/或setpgrp
的调用,一些文件描述符的杂耍,可能还有一些其他的回转。如果你在C.工作,所有这些都由login_tty
处理
好消息是,Pythonpty
模块中有一个方法可以为您完成所有这些工作。该方法是pty.spawn
。它在Python 2和3中可用。我链接到了Python3文档,因为它好多了,还包括一个示例程序。pty.spawn
基本上表现为fork
、exec
、openpty
和login_tty
的组合。
如果你重新设计你的程序,使用pty.spawn
来启动ngrok
,那么我敢肯定你会得到你想要的行为。