Python中使用pexpect通过scp传输文本文件时传输中断怎么办?

最近搞 jenkins,发现用 python 的 pexpect 蛮方便的,但是发现问题用 scp 传文件只传了一半,代码如下:

def scp(user,host,password):
    child = pexpect.spawn("scp  " + "/root/.jenkins/workspace/item1111/fuck.css  "+user+"@"+host+":"+codePath+"/fuck.css.new")
    ret = child.expect([pexpect.TIMEOUT, user])
    if ret == 1:
        child.sendline(password)
        ret = child.expect([pexpect.TIMEOUT, pexpect.EOF])
    child.close()

然后排查了半天发现fuck.css只传了半个就结束了,坑啊,没办法暂时只能换种方式传文件(用 curl 了汗),

但是为了要让 scp 能完整传文件我下班后 gooooooooogle 试了半天还是没办法,于是到论坛来问了:

有什么办法让 scp 能完整传文件?


Python中使用pexpect通过scp传输文本文件时传输中断怎么办?

8 回复

Python 大神去哪里了,我记得坛子里很多人生苦短我用 Python 者来着的


遇到pexpect配合scp传文本文件中断的问题,核心在于scp的交互超时和缓冲区限制。scp在传输过程中会间歇性停顿,而pexpect的默认超时时间较短,容易误判为传输完成或超时。同时,如果传输的数据量较大,pexpect的缓冲区可能被填满,导致后续输出无法被捕获,进而引发超时或异常。

解决的关键是调整pexpect的等待逻辑和缓冲区大小。下面是一个改进后的示例代码,它通过延长超时时间、使用expecttimeout参数动态等待传输完成,并适当增大缓冲区:

import pexpect
import sys

def scp_transfer_with_retry(local_file, remote_file, host, user, password, port=22):
    """
    使用pexpect通过scp传输文件,增加超时和缓冲区处理
    """
    # 构建scp命令
    cmd = f'scp -P {port} {local_file} {user}@{host}:{remote_file}'
    
    try:
        # 启动子进程,设置较大的缓冲区(例如1MB)
        child = pexpect.spawn(cmd, timeout=60, maxread=1048576)
        
        # 等待密码提示
        index = child.expect(['password:', pexpect.EOF, pexpect.TIMEOUT])
        if index == 0:
            child.sendline(password)
        elif index == 1:
            print("连接失败或命令立即退出")
            return False
        elif index == 2:
            print("等待密码提示超时")
            return False
        
        # 动态等待传输完成:持续检查是否出现EOF(传输结束)或超时
        while True:
            try:
                # 每次等待一小段时间,避免长时间阻塞
                child.expect(pexpect.EOF, timeout=5)
                break  # 传输完成,退出循环
            except pexpect.TIMEOUT:
                # 传输仍在进行,继续等待
                continue
            except Exception as e:
                print(f"传输过程中出现异常: {e}")
                return False
        
        # 检查退出状态
        child.wait()
        if child.exitstatus == 0:
            print("文件传输成功")
            return True
        else:
            print(f"传输失败,退出码: {child.exitstatus}")
            return False
            
    except pexpect.ExceptionPexpect as e:
        print(f"pexpect异常: {e}")
        return False

# 使用示例
if __name__ == "__main__":
    success = scp_transfer_with_retry(
        local_file='./test.txt',
        remote_file='/tmp/test.txt',
        host='192.168.1.100',
        user='username',
        password='yourpassword'
    )
    if not success:
        sys.exit(1)

主要改进点:

  1. 延长超时:将spawntimeout设为60秒,避免短时间无响应就超时。
  2. 增大缓冲区maxread=1048576(1MB)防止大数据量时缓冲区溢出。
  3. 动态等待传输完成:使用循环配合短超时的expect来持续检查传输状态,直到遇到EOF(表示scp进程结束)。
  4. 异常处理:捕获并处理可能的pexpect异常和scp退出状态。

如果文件很大或网络不稳定,可以考虑在循环等待中加入进度检测(例如通过文件大小变化判断),或者改用rsync等更健壮的工具。不过对于大多数文本文件传输,上述调整应该能解决中断问题。

一句话总结:调大超时和缓冲区,用循环动态等待传输完成。

尝试一下 Fabric,项目中使用体验非常好

目前猜测,不应该用 EOF 作为 expect,应该用 100 代表的百分百完成作为 expect。

Python 的 fabric 真好,相见恨晚

处理好 stdin scp 只要 wait 就行了

第二次 ret 是多少,试试把 timeout 设长点。

linux 版 powershell,管理员权限运行 Install-Module winscp 即可。

[测试通过的脚本例子]
#----------------------------
#用户输入部分
$本地文件_路径 = ‘d:’
$本地文件_文件名 = ‘a1.txt’
$本地文件_绝对路径名 = join-path -Path $本地文件_路径 -ChildPath $本地文件_文件名

$远程路径 = ‘/UPLOAD/’
$ftp 服务器 = ‘192.168.1.1’
$用户名 = ‘a’
$用户密码明文 = ‘测试通过-2017-11’
#----------------------------
#脚本 main
$用户密码密文 = ConvertTo-SecureString $用户密码明文 -AsPlainText -Force
$我的登陆凭据 = New-Object System.Management.Automation.PSCredential ($用户名,$用户密码密文)
$ftp 连接参数 = new-WinSCPSessionOption -Protocol Ftp -HostName $ftp 服务器 -Credential $我的登陆凭据
$ftp 连接 = new-WinSCPSession -SessionOption $ftp 连接参数

$传输参数_二进制 = New-WinSCPTransferOption -TransferMode Binary
Send-WinSCPItem -WinSCPSession $ftp 连接 -LocalPath $本地文件_绝对路径名 -RemotePath “$远程路径” -TransferOptions $传输参数_二进制
#上传用 Send-WinSCPItem

Receive-WinSCPItem -WinSCPSession $ftp 连接 -RemotePath “$远程路径 /a2.txt” -LocalPath “d:\j”
#下载用 Receive-WinSCPItem



Remove-WinSCPSession -WinSCPSession $ftp 连接 #收工下班

推荐理由:

1 支持 ftp,sftp,webdav。

2 无需先下载整个文件,winscp 模块支持,从 ftp 服务器上,获取文件的校验码,从而得知是否文件是变化的。
有 Get-WinSCPItemChecksum 从而可以同步文件。
网页 https://winscp.net/eng/docs/protocols 的,Checksum calculation 章节,详细说明了这个内容。

3 支持命令行显示 ftp 完成进度,文件权限,文件掩码,传输限速,是否覆盖。文件传输模式( ascii or 二进制)详见此命令:
New-WinSCPTransferOption

回到顶部