探索 Expect 与 Tk 的结合应用
1. 引言
在现代软件开发中,图形用户界面(GUI)的重要性不言而喻。Tk 作为一种强大的 Tcl 扩展,为开发者提供了构建 X Window System 用户界面的能力。而 Expect 则在自动化交互方面表现出色。将 Expect 和 Tk 结合起来,能够创造出功能强大且用户友好的应用程序,这便是 Expectk 的核心。
2. Tk 技术概述
Tk 提供了一系列命令来构建 X Window System 的用户界面。
-
Widgets
:
- 命令创建对象简单,遵循相似风格。例如,创建一个名为 “Get File” 的按钮:
button .getbutton -text "Get File"
- 可使用 `-command` 标志关联命令,如点击按钮调用 `getfile` 命令:
button .getbutton -text "Get File" -command "getfile"
-
其他 Widgets 及命名约定
:
- 除了普通按钮,还有单选按钮、复选按钮等。Tk 还提供了滚动条、菜单、绘图画布等多种 Widgets。
-
命名约定中,“.”类似于文件路径中的“/”,如
.files可包含.files.list和.files.sb。
以下是一个简单的 Tk Widget 创建和布局示例:
frame .type -relief raised -bd 1
radiobutton .passwd -text passwd -variable passwd_cmd \
-value {passwd {cat /etc/passwd}} \
-anchor w -command get_users -relief flat
radiobutton .yppasswd -text yppasswd -variable passwd_cmd \
-value {yppasswd {ypcat passwd}} \
-anchor w -command get_users -relief flat
pack .passwd .yppasswd -in .type -fill x
pack .type -fill x
3. Expectk 简介
- 结合方式 :在构建基于 Tcl 的应用程序时,可以同时包含 Expect 和 Tk 扩展。若系统安装了 Tk,Expect Makefile 会自动构建 Expectk 程序。
-
优势
:
- 避免修改原有程序,减少测试工作量和版本控制问题。
- 继承了 Tk 的诸多优点,如脚本简短、无需冗长编译、易于修改等。
4. Expectk 脚本执行
-
执行方式
:
-
可使用
expectk script命令执行脚本。 -
若脚本首行为
#!/usr/local/bin/expectk,也可直接执行脚本。
-
可使用
-
命令冲突处理
:Tk 和 Expect 都有
send命令,Expectk 让 Tk 的send命令优先。可使用exp_send调用 Expect 的send命令,如:
% exp_send "foo\r"
5. 扩展示例 - tkpasswd
tkpasswd 是一个 Expectk 脚本,用于方便地更改密码。
-
界面布局
:
- 顶部有单选按钮,用于选择本地或 NIS 密码数据库。
- 中间是用户浏览器,显示用户列表。
- 底部有按钮和密码输入框。
-
主要功能函数
:
-
get_users
:重新加载密码数据库。
proc get_users {} {
global sort_cmd passwd_cmd
global selection_line
global nopasswords ;# line #s of users with no passwds
global last_line ;# last line of text box
.names delete 1.0 end
set file [open "| [lindex $passwd_cmd 1] $sort_cmd"]
set last_line 1
set nopasswords {}
while {[gets $file buf] != -1} {
set buf [split $buf :]
if {[llength $buf] > 2} {
# normal password entry
.names insert end [format "%-8s %5d" [lindex $buf 0] [lindex $buf 2]]
if {0 == [string compare [lindex $buf 1] ""]} {
.names tag add nopassword \
{end - 1 line linestart} \
{end - 1 line lineend}
lappend nopasswords $last_line
}
} else {
# +name style entry
.names insert end "$buf"
}
incr last_line
}
close $file
set selection_line 0
}
- `password_set`:设置密码。
proc password_set {} {
global password passwd_cmd selection_line
if {$selection_line == 0} {
feedback "select a user first"
return
}
set user [lindex [.names get selection.first selection.last] 0]
if {[weak-password $password]} {return}
feedback "setting password."
set cmd [lindex $passwd_cmd 0]
spawn -noecho $cmd $user
log_user 0
set last_msg "error in $cmd"
while 1 {
expect {
-nocase "old password:" {
exp_send "[get_old-password] \r"
}
"assword:" {
exp_send "$password\r"
}
-re "(. *) \r\n" {
set last_msg $expect_out(1,string)
}
eof {
break
}
}
}
set status [wait]
if {[lindex $status 3] == 0} {
feedback "set successfully"
} else {
feedback $last_msg
}
}
6. 使用 Tk Widgets 输入密码
在
tkpasswd
脚本中,默认密码输入可见。可通过以下方式模拟密码隐藏:
proc password_backspace {w} {
global password
regexp (.*). $password dummy password dummy
tk_entryBackspace $w
tk_entrySeeCaret $w
}
bind .e <Delete> {password_backspace %W}
bind .e <BackSpace> {password_backspace %W}
bind .e <Any-Key> {
if {"%A" != ""} {
%W insert insert *
set password "[set password]%A"
tk_entrySeeCaret %W
}
}
bind .e <Return> {
password_set $password
set password "" ;# use the password
;# destroy the password
}
7. expect 命令与 Tk 事件循环
在
expect
或
interact
命令等待时,Tk 事件循环仍处于活动状态。这意味着用户可以在脚本执行
expect
命令时与界面交互。
8. expect_background 命令
- 功能 :当输入匹配模式时执行相应操作。
-
示例
:将
$shell产生的输入添加到.text文本框:
expect_background -i $shell -re ".+" {
.text insert end $expect_out(0,string)
}
- 多 Spawn Ids 支持 :可在一个命令中指定多个 Spawn Ids 和模式。
9. 终端模拟器示例
-
简单终端模拟器
:创建两个文本框,分别与
telnet和 shell 进程交互。
# start a shell and text widget for its output
spawn $env(SHELL)
set shell $spawn_id
text .shell -relief sunken -bd 1
pack .shell
# start a telnet and a text widget for its output
spawn telnet
set telnet $spawn_id
text .telnet -relief sunken -bd 1
pack .telnet
expect_background \
-i $telnet -re "[A\x0d]+" {
.telnet insert end $expect_out(0,string)
.telnet yview -pickplace insert
} \
-i $shell -re "[A\x0d]+" {
.shell insert end $expect_out(0,string)
.shell yview -pickplace insert
} \
-i $any_spawn_id "\x0d" {
# discard \r
}
bind Text <Any-Enter> {focus %W}
bind .telnet <Any-KeyPress> {exp_send -i $telnet "%A"}
bind .shell <Any-KeyPress> {exp_send -i $shell "%A"}
-
更智能的终端模拟器
:支持字符寻址,可运行如
emacs和vi等程序。
以下是其部分关键代码:
# tkterm - term emulator using Expect and Tk text widget
set rows 24 ;# number of rows in term
set cols 80 ;# number of columns in term
set term .t ;# name of text widget used by term
# start a shell and text widget for its output
set stty_init "-tabs"
eval spawn $env(SHELL)
stty rows $rows columns $cols < $spawn_out(slave,name)
set term_spawn_id $spawn_id
text $term -width $cols -height $rows
pack $term
set env(LINES) $rows
set env(COLUMNS) $cols
set env(TERM) "tk"
set env(TERMCAP) {tk:
:em=\E[%d;%dH:
:up=\E[A:
:nd=\E[C:
:cl=\E[H\E[J:
:dO=AJ:
:so=\E[7m:
:se=\E[m:
}
set env(TERMINFO) /tmp
set tksre "/tmp/tk.sre"
set file [open $tksre w]
puts $file {tk,
eup=\E [%p1%d; %p2%dH,
euul=\E[A,
euf1=\E [C,
clear=\E[H\E[J,
ind=\n,
cr=\r,
smso=\E[7m,
rmso=\E[m,
}
close $file
catch {exec tic $tksrc}
exec rm $tksrc
10. 终端模拟器用于测试和自动化
可使用终端模拟器部分或完全自动化字符图形应用程序。例如,等待第一行出现
%
提示符:
proc term_chars_changed {} {
uplevel #0 set test-pats 1
}
while 1 {
if {!$test-pats} {tkwait var test-pats}
set test-pats 0
if {[regexp "%" [$term get 1.0 1.end]]} break
}
11. term_expect 过程
term_expect
过程用于简化上述等待逻辑:
proc term_expect {args} {
set timeout [
uplevel {
if {[info exists timeout]} {
set timeout
} else {
uplevel #0 {
if {[info exists timeout]} {
set timeout
} else {
expr 10
}
}
}
}
]
global term_counter
incr term_counter
global [set strobe _data_[set term_counter]]
global [set tstrobe _timer_[set term_counter]]
proc term_chars_changed {} "uplevel #0 set $strobe 1"
set $strobe 1
set $tstrobe 0
if {$timeout >= 0} {
set mstimeout [expr 1000*$timeout]
after $mstimeout "set $strobe 1; set $tstrobe 1"
}
set timeout_act {}
set argc [llength $args]
if {$argc%2 == 1} {
lappend args {}
incr argc
}
for {set i 0} {$i < $argc} {incr i 2} {
set act_index [expr $i+1]
if {! [string compare timeout [lindex $args $i]]} {
set timeout_act [lindex $args $act_index]
set args [lreplace $args $i $act_index]
incr argc -2
break
}
}
while {! [info exists act]} {
if {! [set $strobe]} {
tkwait var $strobe
set $strobe 0
}
if {[set $tstrobe]} {
set act $timeout_act
} else {
for {set i 0} {$i < $argc} {incr i 2} {
if {[uplevel [lindex $args $i]]} {
set act [lindex $args [incr i]]
break
}
}
}
}
proc term_chars_changed {} {}
if {$timeout >= 0} {
after $mstimeout unset $strobe $tstrobe
} else {
unset $strobe $tstrobe
}
set code [catch {uplevel $act} string]
if {$code > 4} {return -code $code $string}
if {$code == 4} {return -code continue}
if {$code == 3} {return -code break}
if {$code == 2} {return -code return}
if {$code == 1} {return -code error \
-errorinfo $errorInfo \
-errorcode $errorCode $string}
return $string
}
12. 总结
通过将 Expect 和 Tk 结合,我们可以创建出功能强大、用户友好的应用程序。从简单的密码修改工具到复杂的终端模拟器,Expectk 为开发者提供了丰富的可能性。同时,文中介绍的各种技术和方法,如事件循环、背景模式匹配等,也为进一步开发和优化应用程序提供了有力支持。
以下是一个 mermaid 流程图,展示了
tkpasswd
脚本的主要流程:
graph TD;
A[启动脚本] --> B[初始化界面和变量];
B --> C[选择数据库和排序方式];
C --> D[加载用户列表];
D --> E[选择用户];
E --> F[输入密码];
F --> G[检查密码];
G --> H{密码是否有效};
H -- 是 --> I[设置密码];
H -- 否 --> F;
I --> J{密码设置是否成功};
J -- 是 --> K[显示成功信息];
J -- 否 --> L[显示错误信息];
K --> M[继续操作或退出];
L --> F;
13. 练习
为了更好地掌握这些技术,以下是一些练习建议:
1. 为第 447 页的终端模拟器添加滚动条,并使其支持可调整大小的文本框。
2. 修改终端模拟器,使其模拟非 ANSI 兼容的特定终端。
3. 编写一个 UNIX
script
命令的版本,在输出记录到文件时自动去除字符图形。
4. 修改
term_expect
过程,使其不依赖 Tk,使用字符串列表或数组模拟 Tk 文本框,并比较性能。
5. 扩展上一个练习,模拟多个终端,并提供“热键”切换不同终端会话。
6. 编写一个脚本,用于浏览
camp.lang.tcl
新闻组的存档,按日期、主题或作者排序显示主题,并在选择后下载并显示文章。
7. 修改上一个脚本,使文章可以本地保存或缓存,避免重复下载。
8. 修改
tkpasswd
脚本,拒绝包含少于两个数字和两个字母(一个大写和一个小写)的密码。
9. 编写一个过程,在标准输入输入密码时提供与 Tk 中相同的反馈。
探索 Expect 与 Tk 的结合应用
14. 练习解析
下面针对前面提出的练习,给出一些思路和可能的实现方法。
14.1 为终端模拟器添加滚动条并支持可调整大小的文本框
要为终端模拟器添加滚动条,可使用
scrollbar
命令,并将其与文本框关联。支持可调整大小的文本框,可通过设置合适的布局和绑定事件来实现。以下是示例代码:
# 假设已有文本框 .term
scrollbar .term.scroll -command ".term yview"
.term configure -yscrollcommand ".term.scroll set"
pack .term.scroll -side right -fill y
pack .term -side left -fill both -expand true
# 绑定窗口大小改变事件
bind . <Configure> {
# 调整文本框大小
.term configure -width [winfo width .] -height [winfo height .]
}
14.2 修改终端模拟器以模拟非 ANSI 兼容的特定终端
要模拟非 ANSI 兼容的终端,需要修改终端描述信息。可以参考原有的
TERMCAP
和
TERMINFO
设置,根据特定终端的特性进行调整。例如:
set env(TERM) "custom-terminal"
set env(TERMCAP) {custom-terminal:
# 根据特定终端特性设置能力
:up=\E[1A:
:down=\E[1B:
}
14.3 编写 UNIX script 命令版本并去除字符图形
可使用正则表达式过滤掉字符图形。以下是示例代码:
set file [open "output.log" w]
spawn some-command
expect {
-re "([^\x1b\x9b].*?)" {
puts $file $expect_out(1,string)
exp_continue
}
eof {
close $file
}
}
14.4 修改 term_expect 过程使其不依赖 Tk
可以使用字符串列表或数组模拟 Tk 文本框。以下是使用列表模拟的示例:
set terminal_lines {}
# 模拟文本框插入操作
proc term_insert {s} {
global terminal_lines cur_row cur_col
set line [lindex $terminal_lines $cur_row]
set new_line [string replace $line $cur_col [expr $cur_col + [string length $s] - 1] $s]
lset terminal_lines $cur_row $new_line
incr cur_col [string length $s]
}
# 修改 term_expect 过程,使用 terminal_lines 进行测试
14.5 扩展模拟多个终端并提供热键切换
可以使用数组存储多个终端的信息,通过热键切换不同的终端会话。以下是示例代码:
set terminals {}
set current_terminal 0
# 创建多个终端
for {set i 0} {$i < 3} {incr i} {
spawn some-command
lappend terminals $spawn_id
}
# 绑定热键切换终端
bind . <F1> {set current_terminal 0; focus .terminal-$current_terminal}
bind . <F2> {set current_terminal 1; focus .terminal-$current_terminal}
bind . <F3> {set current_terminal 2; focus .terminal-$current_terminal}
14.6 编写浏览新闻组存档脚本
可以使用网络请求库下载新闻组存档,使用 Tk 界面显示主题,并在选择后下载并显示文章。以下是简单的示例:
# 假设已有获取新闻组主题列表的函数 get_topic_list
set topics [get_topic_list]
listbox .topics -listvariable topics
pack .topics
bind .topics <<ListboxSelect>> {
set selected_index [.topics curselection]
set topic [lindex $topics $selected_index]
# 下载并显示文章
set article [download_article $topic]
text .article -text $article
pack .article
}
14.7 修改脚本以支持文章本地保存或缓存
可以使用文件操作将文章保存到本地,下次选择时先检查本地是否存在。以下是示例代码:
proc download_article {topic} {
set cache_file "cache/$topic"
if {[file exists $cache_file]} {
return [read [open $cache_file]]
}
set article [fetch_article_from_network $topic]
set file [open $cache_file w]
puts $file $article
close $file
return $article
}
14.8 修改 tkpasswd 脚本拒绝弱密码
可在
weak-password
过程中添加密码强度检查。以下是示例代码:
proc weak-password {password} {
if {![regexp {[0-9].*[0-9]} $password] || ![regexp {[a-z].*[A-Z]} $password]} {
feedback "Password must contain at least two digits and two alphabetic characters (one uppercase and one lowercase)."
return 1
}
return 0
}
14.9 在标准输入输入密码时提供相同反馈
可使用 ANSI 转义序列隐藏输入的密码,并显示星号。以下是示例代码:
proc read_password {} {
set password ""
while 1 {
puts -nonewline "Enter password: "
flush stdout
exec stty -echo
set char [gets stdin]
exec stty echo
if {$char eq "\r" || $char eq "\n"} {
break
} elseif {$char eq "\b"} {
if {$password ne ""} {
set password [string range $password 0 end-1]
puts -nonewline "\b \b"
}
} else {
append password $char
puts -nonewline "*"
}
}
puts ""
return $password
}
15. 实际应用案例分析
为了更好地理解 Expect 与 Tk 结合的实际应用,下面分析一个简单的自动化测试工具案例。
15.1 需求描述
开发一个自动化测试工具,用于测试一个命令行程序的不同输入组合,并显示测试结果。
15.2 实现思路
- 使用 Tk 构建用户界面,包括输入框、按钮和结果显示框。
- 使用 Expect 自动化执行命令行程序,并捕获输出。
- 将测试结果显示在 Tk 界面上。
15.3 示例代码
# 创建 Tk 界面
package require Tk
# 输入框
entry .input -width 30
pack .input
# 按钮
button .run -text "Run Test" -command {run_test}
pack .run
# 结果显示框
text .result -width 50 -height 10
pack .result
# 运行测试函数
proc run_test {} {
set input [.input get]
spawn some-command $input
expect {
-re "(.*)" {
.result insert end $expect_out(1,string)
}
eof {
# 测试结束
}
}
}
# 进入 Tk 事件循环
tk mainloop
以下是一个 mermaid 流程图,展示了自动化测试工具的主要流程:
graph TD;
A[启动工具] --> B[输入测试参数];
B --> C[点击运行按钮];
C --> D[使用 Expect 执行命令行程序];
D --> E{程序是否结束};
E -- 否 --> D;
E -- 是 --> F[捕获程序输出];
F --> G[将结果显示在 Tk 界面上];
G --> H[继续测试或退出];
16. 性能优化与注意事项
在使用 Expect 与 Tk 结合开发应用程序时,需要注意以下性能优化和注意事项:
16.1 性能优化
-
合理使用事件循环
:避免在事件循环中执行耗时操作,可使用
after命令将耗时操作放到后台执行。
after 100 {
# 耗时操作
}
-
减少不必要的模式匹配
:在使用
expect和expect_background时,尽量减少不必要的模式匹配,避免性能开销。
16.2 注意事项
- 命令冲突 :如前面提到的,Tk 和 Expect 可能存在命令冲突,要注意使用正确的命令。
- 资源管理 :及时关闭打开的文件、进程等资源,避免资源泄漏。
17. 未来发展趋势
随着软件开发技术的不断发展,Expect 与 Tk 的结合可能会有以下发展趋势:
-
与现代框架集成
:可能会与更多现代的软件开发框架集成,如 Python 的 GUI 框架,提供更强大的功能。
-
支持更多平台
:扩展对更多操作系统和平台的支持,提高应用程序的兼容性。
-
智能化和自动化程度提升
:进一步提高自动化和智能化程度,减少人工干预。
18. 总结与展望
通过本文的介绍,我们深入了解了 Expect 与 Tk 的结合应用,从基本的 Tk 技术概述到具体的示例代码,再到练习和实际应用案例分析,展示了这种结合的强大功能和广泛应用场景。
在未来的开发中,我们可以充分利用 Expect 与 Tk 的优势,开发出更加高效、智能、用户友好的应用程序。同时,不断探索新的技术和方法,进一步优化和扩展应用程序的功能。希望本文能为开发者提供有价值的参考,帮助大家在软件开发中取得更好的成果。
以下是一个表格,总结了 Expect 与 Tk 结合的主要优点和应用场景:
| 优点 | 应用场景 |
| ---- | ---- |
| 构建用户友好的 GUI | 密码修改工具、自动化测试工具 |
| 实现自动化交互 | 终端模拟器、自动化脚本 |
| 脚本简短、易于修改 | 快速开发和迭代 |
| 避免修改原有程序 | 对现有命令行程序进行 GUI 封装 |
超级会员免费看
6

被折叠的 条评论
为什么被折叠?



