使用scp的Expect脚本无法完整复制全部文件的问题排查
首先,咱们先揪出最致命的问题:你脚本里的expect eof { return}这行代码,return会在第一个scp任务完成后直接退出整个脚本!这就是为什么加了set timeout -1后,只能复制第一个文件就停的原因——第一个scp刚结束,脚本就直接返回退出了,后面的循环根本没机会执行。
修复步骤1:移除导致提前退出的return
把这行:
expect eof { return}
改成:
expect eof
或者如果想明确继续循环,可以写expect eof { continue},不过expect eof本身执行完就会自动进入下一次循环,不需要额外的continue。
修复步骤2:重构参数处理逻辑,避免索引错误
你原来手动维护i和y索引的方式很容易出错,比如lindex $argv $i+1在Expect里不会自动计算数值(它会把$i+1当成字符串索引,比如1+1而不是2),这可能导致你的目录/文件列表出现异常(比如你生成的DIRECTORIES里第一个目录重复了两次)。
咱们换成更简洁可靠的两两分组遍历方式,直接把$argv里的元素按「目录、文件」的顺序分组:
set myDirs {} set myFiles {} # 自动将argv中的元素两两分组,dir对应目录,file对应文件 foreach {dir file} $argv { lappend myDirs $dir lappend myFiles $file } # 过滤空元素(如果有的话) set DIRECTORIES [lsearch -all -inline -not -exact $myDirs {}] set FILES [lsearch -all -inline -not -exact $myFiles {}]
这样就完全避免了手动计算索引的错误,保证目录和文件的对应关系绝对正确。
修复步骤3:处理主机密钥确认(可选但重要)
如果是第一次连接目标主机,scp会弹出Are you sure you want to continue connecting (yes/no)?的提示,你的原脚本没处理这种情况,会导致脚本卡住。咱们把expect的逻辑改成多分支处理:
foreach file $FILES dir $DIRECTORIES { puts " Fichier : $file et repertoire : $dir" spawn scp -p "$mySource/$file" "$myDest/$dir" expect { # 处理首次连接的主机密钥确认 "Are you sure you want to continue connecting (yes/no)?" { send "yes\r" exp_continue ;# 继续等待密码提示 } # 处理密码输入 -re "(.*)assword: " { sleep 1 send -- "$mdproot\r" } } expect eof }
修复步骤4:设置合理的超时时间
在脚本开头添加set timeout -1,让Expect永不超时,避免大文件复制时因为默认10秒超时导致复制中断:
#!/usr/bin/expect -f set timeout -1 ;# 永不超时,适配大文件复制 set mdproot "****" set mySource "****" set myDest "****"
完整修复后的脚本
把这些修改整合起来,最终的脚本应该是这样的:
#!/usr/bin/expect -f set timeout -1 set mdproot "****" set mySource "****" set myDest "****" set myDirs {} set myFiles {} foreach {dir file} $argv { lappend myDirs $dir lappend myFiles $file } set DIRECTORIES [lsearch -all -inline -not -exact $myDirs {}] set FILES [lsearch -all -inline -not -exact $myFiles {}] foreach file $FILES dir $DIRECTORIES { puts " Fichier : $file et repertoire : $dir" spawn scp -p "$mySource/$file" "$myDest/$dir" expect { "Are you sure you want to continue connecting (yes/no)?" { send "yes\r" exp_continue } -re "(.*)assword: " { sleep 1 send -- "$mdproot\r" } } expect eof }
为什么原来的脚本复制到第4个就停?
原脚本没有设置超时,默认超时是10秒,如果前面的文件复制时间超过10秒,Expect会超时退出;再加上参数处理的索引错误,可能导致后续的目录/文件对应关系混乱,最终脚本提前终止。现在修复后,这些问题都能解决。
内容的提问来源于stack exchange,提问作者lL. Bob

