运行 在哑终端中使用 Python subprocess.Popen 和 pty 进行交互 Bash

Run interactive Bash in dumb terminal using Python subprocess.Popen and pty

这个问题类似于,除了我想在“哑”终端(TERM=dumb)中运行 Bash,并且不把tty进入原始模式。

下面的代码是我的尝试。该代码类似于链接问题中给出的解决方案,主要区别在于它没有将 tty 置于原始模式,并设置 TERM=dumb.

import os
import pty
import select
import subprocess
import sys

master_fd, slave_fd = pty.openpty()

p = subprocess.Popen(['bash'],
                     stdin=slave_fd,
                     stdout=slave_fd,
                     stderr=slave_fd,
                     # Run in a new process group to enable bash's job control.
                     preexec_fn=os.setsid,
                     # Run bash in "dumb" terminal.
                     env=dict(os.environ, TERM='dumb'))

while p.poll() is None:
    r, w, e = select.select([sys.stdin, master_fd], [], [])
    if sys.stdin in r:
        user_input = os.read(sys.stdin.fileno(), 10240)
        os.write(master_fd, user_input)
    elif master_fd in r:
        output = os.read(master_fd, 10240)
        os.write(sys.stdout.fileno(), output)

上面的代码有两个问题:

我该如何解决这些问题?

这正是不将 tty 置于 raw 模式的副作用。通常处理 pty 的程序(如 )会将外部 tty 置于 raw 模式。

  • 您的 Python 脚本的 tty(或 pty)回显您输入的内容和新的 pty 第二次回显。您可以在新的 pty 上禁用 ECHO。例如:

    $ python3 using-pty.py
    bash-5.1$ echo hello
    echo hello
    hello
    bash-5.1$ stty -echo
    stty -echo
    bash-5.1$ echo hello    # <-- no double echo any more
    hello
    bash-5.1$ exit
    exit
    
  • 您的 Python 脚本的 tty 不在 raw 模式下,因此当您按 ctrl-d Python 不会得到文字 ctrl-d ('[=13=]4')。相反,Python 将达到 EOF,而 read() returns 将达到一个空字符串。因此,要使生成的 shell 退出,您可以

    user_input = os.read(sys.stdin.fileno(), 10240)
    if not user_input:
         # explicitly send ctrl-d to the spawned process
         os.write(master_fd, b'')
    else:
         os.write(master_fd, user_input)
    
  • 同样,Python 的 tty 不在 raw 模式下,所以当您按 ctrl-c,它不会得到文字 ctrl-c ('[=15=]3')。相反,它被杀死了。作为解决方法,您可以捕获 SIGINT.

    def handle_sigint(signum, stack):
        global master_fd
        # send ctrl-c
        os.write(master_fd, b'')
    signal.signal(signal.SIGINT, handle_sigint)