【问题标题】:Subprocess polluting the parent terminal when using pty使用pty时子进程污染父终端
【发布时间】:2020-03-18 17:06:20
【问题描述】:

示例

我注意到 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)会导致预期的行为。

问题

  1. 如果为子进程提供的stdout/err/in 已更改,为什么子进程知道如何访问父终端?
  2. 如何在 python 中克服这个问题?

【问题讨论】:

    标签: python-3.x linux subprocess pty


    【解决方案1】:

    我不愿意运行从某个随机站点下载的可执行文件,但我愿意打赌ngrok 明确打开并写入/dev/tty 以显示其连接信息。

    /dev/tty 设备指的是进程的“控制终端”,它是进程从其父进程继承的东西之一。重新分配孩子的stdinstdoutstderr 不会影响其控制终端。所以在这种情况下,孩子保留与父母相同的控制终端,当孩子打开并写入/dev/tty时,输出直接进入父母的屏幕,而不是通过孩子的stdoutstderr或你的伪-终端。

    要实现您要查找的内容,您需要将孩子与父母的控制终端分开,并将伪终端的从端建立为孩子的控制终端。这涉及到对setsid 和/或setpgrp 的调用、一些文件描述符的杂耍以及可能的其他一些旋转。如果您使用 C 语言,所有这些都由 login_tty 处理。

    好消息是 Python pty 模块中有一个方法可以为您完成所有这些事情。那个方法是pty.spawn。它在 Python 2 和 3 中可用。我已经链接到 Python 3 文档,因为它要好得多,并且包含一个示例程序。 pty.spawn 基本上表现为forkexecopenptylogin_tty 的组合。

    如果你修改你的程序以使用pty.spawn 来启动ngrok,那么我很确定你会得到你想要的行为。

    【讨论】:

    • 谢谢。 ngrok 直接写信给 /dev/tty 的假设,这只是一个可以解释 ngrok 行为的理论还是可以解决某些问题的更通用的设计模式? (我假设只是让与主终端分离变得更加困难本身并不是设计动机)?
    • 打开/dev/tty 不是很常见,但如果您的程序需要控制终端屏幕上文本的位置和/或视觉属性而您不能,那么它是首选解决方案,或不想,取决于附加到终端的标准 I/O 流之一。 /dev/tty 也可以在您想要与用户进行带外交互时使用,特别是如果您想要控制该交互的视觉性质,这就是为什么 getpass(和某些平台上的 getpassphrase)使用它。
    猜你喜欢
    • 2016-06-29
    • 1970-01-01
    • 2020-07-14
    • 1970-01-01
    • 2023-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多