【问题标题】:Run interactive Bash in dumb terminal using Python subprocess.Popen and pty使用 Python subprocess.Popen 和 pty 在哑终端中运行交互式 Bash
【发布时间】:2021-12-25 07:55:35
【问题描述】:

这个问题类似于Run interactive Bash with popen and a dedicated TTY Python,除了我想在“哑”终端(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)

上面的代码有两个问题:

  • 无论用户输入什么,代码都会重新回显。例如,如果用户输入printf '',上面的代码将在下一行打印printf '',然后再打印下一个 bash 提示符。
  • CtrlcCtrld 的行为与 bash 中的预期不同。

我应该如何解决这些问题?

【问题讨论】:

    标签: python linux bash tty pty


    【解决方案1】:

    这正是不将 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 ( '\004')。相反,Python 会到达 EOF 并且 read() 返回一个空字符串。所以要让生成的 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'\04')
      else:
           os.write(master_fd, user_input)
      
    • 同样,Python 的 tty 不在 raw 模式,所以当你按下 ctrl-c 时,它不会得到文字 ctrl-c kbd> ('\003')。反而被杀了。作为一种解决方法,您可以捕获 SIGINT

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

    【讨论】:

    • 我发现我可以像这样使用termios来禁用ECHO:import termios; slave_attr = termios.tcgetattr(slave_fd); slave_attr[3] = slave_attr[3] &amp; ~termios.ECHO; termios.tcsetattr(slave_fd, termios.TCSADRAIN, slave_attr).
    • 使用您的 SIGINT 处理程序,为什么 Ctrl-c 会导致显示 ^C^C 而不是仅显示 ^C(正如 TERM=dumb bash 所预期的那样)?
    • 每个 pty 输出一个 ^C
    • 好吧,我发现我必须做stty -echoctl来防止第二个^C的回声。
    • 假设我将['bash'] 替换为['ssh', 'user@example.com']。为什么 Ctrl-c 会导致脚本退出而不是在 SSH 会话中显示下一个提示符?
    猜你喜欢
    • 1970-01-01
    • 2017-05-23
    • 2019-12-23
    • 1970-01-01
    • 2013-04-16
    • 2017-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多