You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么自定义SSH封装脚本中运行tmux、vim会输出乱码?

问题根因

你看到的乱码是终端控制应答序列:tmux、vim这类全屏终端程序启动后会主动向终端发送特性查询、光标位置查询等CSI控制指令,原生终端收到这类指令后会悄悄返回应答给程序,不会将应答内容打印到屏幕上。
你的脚本没有修改本地终端的运行模式,本地终端默认处于「行缓冲+输入回显」的标准模式,所以会把本该传给远程程序的应答序列直接输出到屏幕,就出现了你看到的乱码。

修复方案

使用Python标准库的termios、tty模块修改本地终端模式,程序退出前恢复原始终端属性即可,同时优化二进制读写避免编码错误,修改后的完整代码如下:

import os
import pty
import select
import signal
import subprocess
import sys
import termios
import tty

master_fd, slave_fd = pty.openpty()
process = subprocess.Popen(['ssh'] + sys.argv[1:],
                           stdin=slave_fd,
                           stdout=slave_fd,
                           stderr=subprocess.STDOUT,
                           preexec_fn=os.setsid)

# 保存本地原始终端属性,退出时恢复
orig_tty_attr = termios.tcgetattr(sys.stdin.fileno())

def restore_tty():
    termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, orig_tty_attr)

def sigint_handler(_signum, _frame):
    os.write(master_fd, b'\03')  # Send Ctrl-c.
signal.signal(signal.SIGINT, sigint_handler)

def sigtstp_handler(_signum, _frame):
    os.write(master_fd, b'\x1A')  # Send Ctrl-z.
signal.signal(signal.SIGTSTP, sigtstp_handler)

def sigchld_handler(_signum, _frame):
    restore_tty()
    process.wait()
    sys.exit(process.returncode)
signal.signal(signal.SIGCHLD, sigchld_handler)

# 处理窗口大小变化,同步给远程终端
def sigwinch_handler(_signum, _frame):
    winsize = termios.tcgetwinsize(sys.stdin.fileno())
    termios.tcsetwinsize(master_fd, winsize)
signal.signal(signal.SIGWINCH, sigwinch_handler)

try:
    # 将本地终端设置为raw模式,关闭输入回显、行缓冲
    tty.setraw(sys.stdin.fileno())
    # 初始化同步一次窗口大小
    termios.tcsetwinsize(master_fd, termios.tcgetwinsize(sys.stdin.fileno()))
    
    while process.poll() is None:
        r, w, e = select.select([sys.stdin, master_fd], [], [])
        if sys.stdin in r:
            user_input = os.read(sys.stdin.fileno(), 4096)
            if not user_input:
                os.write(master_fd, b'\04')  # Send Ctrl-d.
            else:
                os.write(master_fd, user_input)
        if master_fd in r:
            data = os.read(master_fd, 4096)
            # 直接写二进制到标准输出,避免解码出错
            sys.stdout.buffer.write(data)
            sys.stdout.flush()
finally:
    # 任何退出场景都恢复原始终端属性
    restore_tty()
修改点说明
  • 新增终端属性保存/恢复逻辑,避免脚本退出后本地终端无法正常使用
  • 启动时将本地终端设置为raw模式,终端会自动处理控制序列的应答传递,不再打印乱码
  • 直接操作标准输入输出的二进制buffer,避免特殊控制字符解码出错
  • 新增窗口大小变化信号处理,远程tmux、vim会自动适配窗口大小

内容的提问来源于stack exchange,提问作者Flux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 07:27:00