Python实现交互式SSH Shell:修复Paramiko方向键与窗口自适应问题

周叙
周叙
发布时间:2026-10-13 | 5人浏览过
原创

本文介绍基于Python Paramiko模块实现交互式SSH Shell的方法。针对官方Demo中方向键响应延迟、粘贴多字节数据异常及终端窗口大小固定等缺陷,提供了修复方案。通过调整输入缓冲区、监听SIGWINCH信号同步PTY大小以及处理select系统调用中断,实现了流畅的终端交互体验。

近期在研究Unix环境编程,重点梳理进程创建机制,并尝试通过最底层的方式实现SSH命令的执行逻辑。

#coding=utf8
 
'''
用python实现了一个简单的shell,了解进程创建
类unix 环境下 fork和exec 两个系统调用完成进程的创建
'''
 
import sys, os
 
 
def myspawn(cmdline):
 argv = cmdline.split()
 if len(argv) == 0:
  return 
 program_file = argv[0]
 pid = os.fork()
 if pid < 0:
  sys.stderr.write("fork error")
 elif pid == 0:
  # child
  os.execvp(program_file, argv)
  sys.stderr.write("cannot exec: "+ cmdline)
  sys.exit(127)
 # parent
 pid, status = os.waitpid(pid, 0)
 ret = status >> 8 # 返回值是一个16位的二进制数字,高8位为退出状态码,低8位为程序结束系统信号的编号
 signal_num = status & 0x0F
 sys.stdout.write("ret: %s, signal: %s\n" % (ret, signal_num))
 return ret
 
 
def ssh(host, user, port=22, password=None):
 if password:
  sys.stdout.write("password is: '%s' , plz paste it into ssh\n" % (password))
 cmdline = "ssh %s@%s -p %s " % (user, host, port)
 ret = myspawn(cmdline)
 
 
if __name__ == "__main__":
 host = ''
 user = ''
 password = ''
 ssh(host, user, password=password)

当前项目需求要求在客户端集成交互式SSH功能。具体流程为:客户端向服务器申请可用资源,服务器返回IP、端口及密码信息,随后客户端利用这些信息直接登录目标机器进行操作。该程序基于Paramiko模块开发。

查阅Paramiko源码包demos目录后,发现interactive.py(原名为demo.py)提供了交互式Shell的实现示例。但在实际使用中存在若干Bug,因此我对interactive.py进行了修改,移除了Windows相关代码,使其仅适用于Linux环境。修改后的代码如下:

#coding=utf-8
import socket
import sys
import os
import termios
import tty
import fcntl
import signal
import struct
import select
 
now_channel = None
 
def interactive_shell(chan):
 posix_shell(chan)
 
 
def ioctl_GWINSZ(fd):
 try:
  cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'aaaa'))
 except:
  return
 return cr
 
 
def getTerminalSize():
 cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
 return int(cr[1]), int(cr[0])
 
 
def resize_pty(signum=0, frame=0):
 width, height = getTerminalSize()
 if now_channel is not None:
  now_channel.resize_pty(width=width, height=height)
 
 
 
def posix_shell(chan):
 global now_channel
 now_channel = chan
 resize_pty()
 signal.signal(signal.SIGWINCH, resize_pty) # 终端大小改变时,修改pty终端大小
 stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) # stdin buff置为空,否则粘贴多字节或者按方向键的时候显示不正确
 fd = stdin.fileno()
 oldtty = termios.tcgetattr(fd)
 newtty = termios.tcgetattr(fd)
 newtty[3] = newtty[3] | termios.ICANON
 try:
  termios.tcsetattr(fd, termios.TCSANOW, newtty)
  tty.setraw(fd)
  tty.setcbreak(fd)
  chan.settimeout(0.0)
  while True:
   try:
    r, w, e = select.select([chan, stdin], [], [])
   except:
    # 解决SIGWINCH信号将休眠的select系统调用唤醒引发的系统中断,忽略中断重新调用解决。
    continue
   if chan in r:
    try:
     x = chan.recv(1024)
     if len(x) == 0:
      print 'rn*** EOFrn',
      break
     sys.stdout.write(x)
     sys.stdout.flush()
    except socket.timeout:
     pass
   if stdin in r:
    x = stdin.read(1)
    if len(x) == 0:
     break
    chan.send(x)
 finally:
  termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)

使用示例如下:

#coding=utf8
import paramiko
import interactive
 
 
#记录日志
paramiko.util.log_to_file('/tmp/aaa')
#建立ssh连接
ssh=paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('192.168.1.11',port=22,username='hahaha',password='********',compress=True)
 
#建立交互式shell连接
channel=ssh.invoke_shell()
#建立交互式管道
interactive.interactive_shell(channel)
#关闭连接
channel.close()
ssh.close()

interactive.py代码主要修复了以下三个关键问题:

1、方向键输入异常修复。读取键盘输入时,方向键单次按键会产生3个字节数据。原逻辑中,select捕捉到标准输入变化后,每次仅处理1个字节,剩余数据滞留输入缓冲区,导致下次按键时才发送完整序列,造成方向键操作显示延迟。多字节粘贴也存在相同原理的问题。解决方案是将输入缓冲区大小设置为0,实现无缓冲即时发送,从而消除显示延迟。

2、终端窗口大小自适应。Paramiko的channel创建的pty(伪终端)默认尺寸为width=80, height=24,导致登录后可视区域小且固定,使用vim等编辑器体验不佳。channel提供resize_pty方法,但需动态获取当前终端尺寸。当终端窗口改变时,系统会向前台进程组发送SIGWINCH信号。在信号处理函数中获取当前尺寸并同步至pty,使pty内的进程也能感知窗口变化并接收SIGWINCH信号,从而实现自适应。

3、处理‘慢’设备读写阻塞。对于pipe、终端设备或网络连接等‘慢’设备,读写可能因数据缺失或缓冲区满而需要等待。SSH通道属于此类。当网络无通信时,select调用处于阻塞状态;若此时终端窗口大小变化并触发SIGWINCH信号,进程被唤醒,select会抛出异常(错误码4,'Interrupted system call')。此异常仅出现一次,重新调用select即可恢复正常。因此,捕获select异常后重试select可解决该问题。

以上即为Python获取交互式SSH Shell的完整实现与优化方案,供读者参考。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表