expect 是自动化 sftp 交互的首选工具,通过匹配提示符(如 password:、yes/no)并发送响应实现免人工操作;支持密码安全传递、超时处理、错误捕获、日志记录,并可与 shell 脚本集成提升健壮性与可维护性。

Expect 是处理交互式命令(比如需要手动输入密码的 sftp)最直接有效的工具。它能模拟用户输入,自动响应提示,把原本需要人工介入的 SFTP 拉取过程变成可调度、可复用的自动化流程。
Expect 基础:匹配提示 + 发送密码
Expect 脚本核心是等待特定字符串(如 Password:),然后发送对应响应。SFTP 登录时常见提示有:
-
password:(小写,OpenSSH 默认) -
Password:(首字母大写,部分旧版本或定制服务) -
Are you sure you want to continue connecting (yes/no)?(首次连接 SSH host key 未知时)
一个最小可用脚本示例(保存为 sftp_get.exp):
#!/usr/bin/expect -f
set timeout 30
set host [lindex $argv 0]
set user [lindex $argv 1]
set passwd [lindex $argv 2]
set remote_file [lindex $argv 3]
set local_file [lindex $argv 4]
<p>spawn sftp $user@$host
expect {
-re ".<em>yes.</em>no.<em>" { send "yes\r"; exp_continue }
-re ".</em>password.<em>" { send "$passwd\r" }
}
expect "$user@</em>"
send "get $remote_file $local_file\r"
expect "sftp>"
send "quit\r"
expect eof</p>注意:exp_continue 让 expect 在匹配到 yes/no 提示后继续等待下一个提示(比如密码),避免流程中断。
安全传递密码:避免明文写在脚本里
直接把密码写进脚本或命令行参数存在泄露风险。更稳妥的做法:
- 用
stty -echo关闭终端回显,在 shell 中交互式输入密码,再传给 expect(推荐用于本地调试) - 从环境变量读取:
set passwd $env(PASSWD),运行前执行PASSWD="xxx" ./sftp_get.exp host user file... - 使用密钥认证替代密码(更安全):生成 SSH 密钥对,用
ssh-copy-id推送公钥,expect 脚本中去掉密码交互逻辑,直接执行 sftp
密钥方式不仅免密码,还能规避 expect 匹配失败问题,是生产环境首选。
增强健壮性:超时、错误捕获与日志
真实环境中网络波动、权限错误、路径不存在都可能导致失败。建议加入:
-
set timeout 60:适当延长超时,避免因慢速网络误判失败 - 用
expect { timeout { exit 1 } ... }显式处理超时分支 - 捕获 sftp 命令返回状态:
expect eof后检查$expect_out(exit_status),非 0 则报错退出 - 加
log_file -a /var/log/sftp_pull.log记录完整交互过程,方便排查
例如在 get 命令后加:
expect {
"Fetching.*100%" { }
timeout { puts "ERROR: File transfer timed out"; exit 1 }
-re ".*No such file.*" { puts "ERROR: Remote file not found"; exit 1 }
}
集成到 Shell 脚本:统一入口 + 参数校验
单独写 expect 不便于维护和调用。通常用一个 shell 包装器封装参数检查、环境准备和 expect 执行:
#!/bin/bash [ $# -lt 5 ] && echo "Usage: $0 host user passwd remote_path local_path" && exit 1 HOST=$1; USER=$2; PASS=$3; REMOTE=$4; LOCAL=$5 <h1>检查本地目录可写</h1><p>dirname "$LOCAL" | xargs mkdir -p 2>/dev/null [ ! -w "$(dirname "$LOCAL")" ] && echo "Cannot write to $(dirname "$LOCAL")" && exit 1</p><h1>执行 expect(密码通过环境变量传递)</h1><p>PASSWD="$PASS" expect ./sftp_get.exp "$HOST" "$USER" "$REMOTE" "$LOCAL"</p>
这样既保留了 shell 的灵活性(参数处理、路径检查、权限验证),又让 expect 专注交互逻辑,职责清晰,也方便加入重试、告警等扩展功能。











