在Expect的interact模式下恢复Ctrl-Z的常规作业控制功能
Expect脚本作业控制问题解决方案
问题概述
在Expect中使用interact命令时,终端会被设置为raw模式,破坏原生作业控制。手册建议拦截Ctrl-Z字符手动调用kill,但希望改为让终端自行发送SIGTSTP信号,通过预先设置的trap处理,同时保留interact传递用户输入给派生程序的功能。另外存在两个疑问:
- 如何在
interact代码块中执行stty isig? -reset \032 {...}的实际作用,为何命名为"reset"却未执行终端重置操作?
关键知识点解析
1. -reset选项的真实作用
interact的-reset选项命名容易产生误导,它的实际功能是:当匹配到指定字符(此处为Ctrl-Z,即\032)时,先将终端模式重置为调用interact前的状态,再执行代码块内的自定义逻辑。这里的"reset"指恢复终端模式,而非重置整个终端环境,代码块中的kill操作是额外添加的业务逻辑。
2. 启用终端SIGTSTP信号的方法
要让终端自行发送SIGTSTP,需在interact期间保留终端的信号生成功能,即开启isig标志。只需在进入interact前执行stty isig配置终端,同时避免强制将终端设为raw模式即可。
修改后的脚本实现
以下是调整后的passwrap.exp,可让终端正常发送SIGTSTP,通过预先设置的trap处理子进程和Expect自身的暂停/恢复:
#!/usr/bin/expect -f set timeout -1 if {$argc < 3} { send_user "Wrapper script for programs that prompt for a password and can't read it from a file/env var.\n" send_user "usage: $argv0 PASSWORD_FILE PASSWORD_PROMPT command args...\n" send_user "Ex: $argv0 ~/7zpass.txt \"Enter password:\" 7zz t encrypted.7z\n" exit 1 } # First arg = password file set password_file [lindex $argv 0] set password_prompt [lindex $argv 1] # Remaining args = command to execute set cmd [lrange $argv 2 end] # Read password set fp [open $password_file r] set password [read $fp] if {[string index $password end] eq "\n"} { set password [string range $password 0 end-1] } close $fp spawn {*}$cmd set child_pid [exp_pid] # 处理SIGTSTP:暂停子进程和Expect自身 trap { catch {exec kill -STOP $child_pid} exec kill -STOP [pid] } SIGTSTP # 处理SIGCONT:恢复子进程 trap { exec kill -CONT $child_pid } SIGCONT # 自动输入密码 expect { $password_prompt { stty -echo < $spawn_out(slave,name) send_user "\n" send -- "$password\r" stty echo < $spawn_out(slave,name) } } # 配置终端保留isig标志,让Ctrl-Z能触发SIGTSTP stty isig # 使用默认interact模式,不强制raw,保留作业控制 interact catch wait result exit [lindex $result 3]
核心调整说明
stty isig:在进入interact前执行,开启终端的信号生成功能,此时按下Ctrl-Z会自动触发SIGTSTP,交给预先设置的trap处理。- 移除原有的
-reset \032拦截逻辑,因为终端现在会自行处理信号,无需手动捕获字符。 - 未添加
-raw选项的interact会使用默认模式,终端不会被强制设为raw,原生作业控制功能得以保留。
内容的提问来源于stack exchange,提问作者basin
相关产品推荐
相关产品推荐

