Expect脚本遍历SFTP文件列表并本地处理的执行异常问题
问题:Expect脚本处理SFTP文件时本地操作未执行/循环不等待完成
需求是连接SFTP服务器,遍历文件列表完成:1. 从SFTP获取文件;2. 在本地执行文件操作,再处理下一个文件。但现有脚本中本地操作从未执行,或循环未等待操作完成。
原脚本代码:
set filelist_path filelist.txt proc slurp {file} { set fh [open $file r] set ret [read $fh] close $fh return $ret } set filelist [slurp $filelist_path] set rundir [file normalize [file dirname .]] ### Start connection to ftp server spawn sftp ${user}@${host} expect "Password:" send "$pass\r" ### Download and manipulate files in the list foreach item $filelist { expect "sftp>" send_user "Processing $item : \n" set filedir [file dirname ${item}] set outdir ${rundir}/tmp${filedir} puts [exec mkdir -p $outdir] send "lcd $outdir \r" send "get -p /data'${item}'\r" send "lcd $rundir \r" puts [exec <some cmd>] puts [exec rm -r ./tmp] } send "quit\r" expect eof
问题原因及修复方案
1. 文件列表读取错误
slurp函数读取整个文件为单个字符串,foreach item $filelist会遍历每个字符而非每行文件路径。需要按行分割并过滤空行:
set filelist [split [slurp $filelist_path] \n] # 过滤空行 set filelist [lsearch -all -inline -not -exact $filelist ""]
2. 未等待SFTP命令执行完成
执行send发送SFTP命令后,必须等待sftp>提示符返回,确认命令执行完毕,再执行本地操作。比如lcd和get命令后都要等待提示符:
send "lcd $outdir\r" expect "sftp>" ;# 等待切换目录完成 send "get -p /data${item}\r" expect "sftp>" ;# 等待文件下载完成 send "lcd $rundir\r" expect "sftp>" ;# 等待切回原目录完成
注意:原代码中/data'${item}'的单引号可能导致路径错误,直接拼接/data${item}即可(如果文件名含特殊字符,需要转义,但先保证路径正确)
3. 本地操作的时机与错误处理
- 本地操作(
<some cmd>)必须在SFTP下载完成后执行,且要确保exec命令不会因错误中断脚本。可以用catch捕获错误:
if {[catch {exec <some cmd>} result]} { send_user "本地命令执行失败: $result\n" } else { send_user "本地命令执行完成: $result\n" }
- 循环内每次删除
./tmp会导致后续文件处理时目录不存在,应该删除当前文件对应的$outdir,或者在所有文件处理完成后统一删除整个tmp目录。
修正后的完整脚本
set filelist_path filelist.txt proc slurp {file} { set fh [open $file r] set ret [read $fh] close $fh return $ret } # 读取并处理文件列表:按行分割+过滤空行 set filelist [split [slurp $filelist_path] \n] set filelist [lsearch -all -inline -not -exact $filelist ""] set rundir [file normalize [file dirname [info script]]] ;# 用info script获取当前脚本目录更可靠 ### Start connection to SFTP server spawn sftp ${user}@${host} expect "Password:" send "$pass\r" expect "sftp>" ;# 等待登录成功进入SFTP交互 ### Download and manipulate files in the list foreach item $filelist { send_user "Processing $item : \n" set filedir [file dirname ${item}] set outdir ${rundir}/tmp${filedir} # 创建本地目录 if {[catch {exec mkdir -p $outdir} mkdir_result]} { send_user "创建目录失败: $mkdir_result\n" continue ;# 跳过当前文件处理 } # 切换SFTP本地工作目录 send "lcd $outdir\r" expect "sftp>" # 下载文件 send "get -p /data${item}\r" expect { "sftp>" { send_user "$item 下载完成\n" } "No such file or directory" { send_user "$item 不存在,跳过\n" send "lcd $rundir\r" expect "sftp>" continue } } # 切回原工作目录 send "lcd $rundir\r" expect "sftp>" # 执行本地操作 if {[catch {exec <some cmd> -- $outdir/[file tail $item]} cmd_result]} { send_user "本地操作失败: $cmd_result\n" } else { send_user "本地操作完成: $cmd_result\n" } # 删除当前文件的临时目录 if {[catch {exec rm -r $outdir} rm_result]} { send_user "删除临时目录失败: $rm_result\n" } } send "quit\r" expect eof
额外注意事项
- 用
[info script]获取当前脚本所在目录,比file dirname .更可靠,避免因执行脚本的工作目录不同导致路径错误。 - 增加了SFTP命令的异常捕获(比如文件不存在的情况),让脚本更健壮。
- 本地命令中最好明确指定处理的文件路径,避免依赖工作目录。
内容的提问来源于stack exchange,提问作者TimoT
相关产品推荐
相关产品推荐

