20、探索 Expect 与 Tk 的结合应用

探索 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 封装 |

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合
先展示下效果 https://pan.quark.cn/s/a4b39357ea24 本项目是本人参加BAT等其他公司电话、现场面试之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的。 除开知识点,一定要准备好以下套路: 个人介绍,需要准备一个1分钟的介绍,包括学习经历、工作经历、项目经历、个人优势、一句话总结。 一定要自己背得滚瓜烂熟,张口就来 抽象概念,当面试官问你是如何理解多线程的时候,你要知道从定义、来源、实现、问题、优化、应用方面系统性地回答 项目强化,至少知识点的比例是五五开,所以必须针对简历中的两个以上的项目,形成包括【架构和实现细节】,【正常流程和异常流程的处理】,【难点+坑+复盘优化】三位一体的组合拳 压力练习,面试的时候难免紧张,可能会严重影响发挥,通过平时多找机会参交流分享,或找人做压力面试来改善 表达练习,表达能力非常影响在面试中的表现,能否简练地将答案告诉面试官,可以通过给自己讲解的方式刻意练习 重点针对,面试官会针对简历提问,所以请针对简历上写的所有技术点进行重点准备 Java基础 JVM原理 集合 多线程 IO 问题排查 Web框架、数据库 Spring MySQL Redis 通用基础 操作系统 网络通信协议 排序算法 常用设计模式 从URL到看到网页的过程 分布式 CAP理论 锁 事务 消息队列 协调器 ID生成方式 一致性hash 限流 微服务 微服务介绍 服务发现 API网关 服务容错保护 服务配置中心 算法 数组-快速排序-第k大个数 数组-对撞指针-最大蓄水 数组-滑动窗口-最小连续子数组 数组-归并排序-合并有序数组 数组-顺时针打印矩形 数组-24点游戏 链表-链表反转-链表相加 链表-...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值