我注意到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
已更改,为什么子进程知道如何访问父终端?/dev/tty
设备是指进程的“控制终端”,这是进程从其父级继承的事物之一。重新分配孩子的stdin
,stdout
和stderr
不会影响其控制终端。因此,在这种情况下,子级保留与父级相同的控制终端,并且当子级打开并写入/dev/tty
时,输出将直接进入父级的屏幕,而不经过子级的stdout
或stderr
或您的伪-终端。要实现所需的功能,您需要将孩子与父母的控制终端离婚,并将伪终端的从属端建立为孩子的控制终端。这涉及到对setsid
和/或setpgrp
的调用,一些文件描述符的杂耍以及可能的其他回转,但是如果您在C中工作,则所有这些工作都由login_tty
处理。
好消息是,Python pty
模块中有一个方法可以为您完成所有这些操作。该方法为pty.spawn
。它在Python 2和3中可用。我已经链接到Python 3文档,因为它的功能要好得多,并且包含示例程序。 pty.spawn
基本上表现为pty.spawn
,fork
,exec
和openpty
的组合。
如果您重做程序以使用login_tty
启动pty.spawn
,那么我很确定您会得到想要的行为。