Expect 编程中的杂项知识与实用技巧
在编程过程中,总会遇到一些零散但又十分实用的知识和技巧。本文将为大家详细介绍在 Expect 编程中,关于随机数生成、库的使用、版本管理、时间处理等方面的内容,同时还会给出相关的命令和变量列表,帮助大家更好地掌握 Expect 编程。
随机数生成
在 Expect 中,有时需要生成随机数,比如在编写游戏脚本时。但 Expect 并没有内置的随机数生成命令,这是因为已经有很多解决方案,而且不同的随机数生成器(RNG)在不同目标之间进行了权衡,用户对随机数的需求也各不相同。
如果 RNG 是一个独立的程序,可以使用
open
或
spawn
来运行它。如果是非交互式且生成随机数流的程序,使用
open
并通过
gets
读取新的随机数;如果是交互式的,根据需要发送命令并使用
expect
获取结果。
如果想要的 RNG 是一个 C 子例程,可以编写一个命令来调用它,然后将其链接到 Expect 中。
如果对随机数质量要求不高,只是希望程序每次运行方式不同,可以使用以下 Tcl 过程:
proc random_init {seed} {
global _ran
set _ran $seed
}
proc random {} {
global _ran
set period 259200
set _ran [expr ($_ran*7141 + 54773) % $period]
expr $_ran/double($period)
}
使用时,首先调用
random_init
用一个正整数种子初始化,例如
random_init [pid]
。初始化后,每次调用
random
都会返回一个新的随机值。
生成随机密码示例
mkpasswd
是一个随 Expect 附带的示例脚本,用于生成新密码并可将其分配给用户。它使用了上述随机数过程的变体
rand
来生成 0 到
n - 1
的随机整数:
proc rand {n} {
global _ran
set period 259200
set _ran [expr ($_ran*7141 + 54773) % $period]
expr int($n*($_ran/double($period)))
}
mkpasswd
接受参数来控制生成密码的长度、数字和字母的数量。以下是生成密码的代码示例:
# initialize password
set password ""
# add digits to the password
for {set i 0} {$i<$minnum} {incr i} {
insertchar [rand 10]
}
# add lowercase letters
for {set i 0} {$i<$minlower} {incr i} {
insertchar [format "%c" [expr 0x61 + [rand 26]]]
}
# add uppercase letters
for {set i 0} {$i<$minupper} {incr i} {
insertchar [format "%c" [expr 0x41 + [rand 26]]]
}
proc insertchar {c} {
global password
set password [linsert $password [rand [expr 1+[llength $password]]] $c]
}
# convert password list to string
set password [join $password ""]
Expect 库
Tcl 支持 Tcl 过程库,Expect 遵循了 Tcl 的模型,提供了两个目录,用户可以在其中创建和使用公开可访问的 Expect 过程或其他数据,目录名分别存储在全局变量
exp_library
和
exp_exec_library
中。
exp_library
目录包含与平台无关的文件,如果通过 NFS 挂载到不同类型的多台计算机上,所有计算机都可以共享相同的通用过程。而
exp_exec_library
目录则特定于某台机器(必要时,还特定于不同的操作系统版本)。
这些库不仅可以存储脚本,还可以存储非可执行数据。例如,检测
cat
命令是否需要
-u
标志时,Expect 会在安装时执行测试,并在
exp_exec_library
目录下留下一个名为
cat_buffers
的文件作为标记。在脚本中可以这样测试:
if [file exists $exp_exec_library/cat_buffers] {
set catflags "-u"
} else {
set catflags ""
}
spawn -open [open "/bin/cat $catflags $fifo" r]
exp_library
和
exp_exec_library
目录名也出现在全局变量
auto-path
中,这使得它们可以被自动加载。如果为 Expect 库生成了索引文件,引用其中定义的过程时会自动加载。
Expect 版本管理
和其他软件一样,Expect 会不时发布新版本以添加新功能、修复 bug。
exp_version
命令可用于处理版本不匹配问题,它可以验证脚本是否适用于当前的 Expect 可执行文件,如果不适用则阻止脚本继续执行。
不带参数调用
exp_version
时,会报告当前版本:
expect1.1> exp_version
5.9.0
可以在脚本中使用
exp_version
命令指定所需版本。版本号由三个用点分隔的数字组成,分别是主版本号、次版本号和一个在版本比较中不起作用的数字。
-
主版本号不同的脚本几乎肯定无法正常工作,
exp_version会生成错误并返回相应消息。 -
次版本号方面,如果脚本编写时使用的次版本号大于当前版本,可能依赖了新功能,脚本可能无法运行,
exp_version会返回错误。
exp_version
还有一个
-exit
标志,可用于强制脚本退出而不是返回错误。
以下是一个版本检查的示例:
if [catch {exp_version 5.3.0} msg] {
puts "warning: $msg"
}
时间戳处理
timestamp
命令可用于生成各种形式的时间表示。不带参数时,它返回自 UNIX 纪元(1970 年 1 月 1 日 UTC)以来的秒数:
expect1.1> timestamp
759382559
使用
-format
标志和描述格式的字符串可以将时间转换为其他形式,通常,以百分号开头的字符会被替换。例如:
expect1.1> timestamp -format "The time of day is %X"
The time of day is 17:05:58
expect1.2> timestamp -format "It is a %A in %B"
It is a Monday in January
Expect 支持的替换字符列表如下:
| 替换字符 | 描述 |
| ---- | ---- |
|
%a
| 缩写的星期几名称 |
|
%A
| 完整的星期几名称 |
|
%b
| 缩写的月份名称 |
|
%B
| 完整的月份名称 |
|
%c
| 日期 - 时间格式(如 Wed Oct 6 11:45:56 1993) |
|
%d
| 月份中的日期(01 - 31) |
|
%H
| 小时(00 - 23) |
|
%I
| 小时(01 - 12) |
|
%j
| 一年中的第几天(001 - 366) |
|
%m
| 月份(01 - 12) |
|
%M
| 分钟(00 - 59) |
|
%p
| am 或 pm |
|
%S
| 秒(00 - 61) |
|
%u
| 星期几(1 - 7,星期一为第一天) |
|
%U
| 星期(00 - 53,第一个星期日为第一周的第一天) |
|
%V
| 星期(01 - 53,ISO 8601 风格) |
|
%w
| 星期几(0 - 6,星期日为 0) |
|
%W
| 星期(00 - 53,第一个星期一为第一周的第一天) |
|
%x
| 日期 - 时间格式(如 Wed Oct 6 1993) |
|
%X
| 时间格式(如 23:59:59) |
|
%y
| 年份(00 - 99) |
|
%Y
| 年份(如 1993) |
|
%Z
| 时区(如果无法确定则为空) |
|
%%
| 百分号 |
当所有日期都以相对于或绝对于纪元的秒数表示时,进行日期算术运算最为方便。可以使用
-seconds
标志对以秒表示的日期进行格式化:
expect1.1> set time [timestamp]
759443622
expect1.2> timestamp -format %X -seconds $time
15:33:42
expect1.3> timestamp -format %X -seconds [expr $time+1]
15:33:43
时间测量
如果想比较不同命令和算法的执行速度,可以使用
timestamp
命令,但 Tcl 提供的内置命令
time
更方便。
time
命令接受一个要执行的命令和一个可选的迭代次数,返回每次迭代的执行时间描述。例如:
expect1.1> time {sleep 7} 3
7000327 microseconds per iteration
使用较高的迭代次数可以平滑每次运行的差异,提供更有用的结果。在某些情况下,运行外部性能监视器也很有用,例如内存或网络使用情况对应用程序可能和 CPU 使用情况一样重要。
Expect 编程中的杂项知识与实用技巧(下半部分)
相关练习
为了帮助大家更好地巩固所学知识,下面提供了一些相关的练习:
1.
使用随机数绘制图表
:利用前面提到的
random
过程,编写一个 Tk 脚本,绘制大量随机值的图表。以时间(对图表宽度取模)作为图表的第二个维度,观察图表中是否存在模式。
2.
查找未文档化的实用工具
:许多 Tcl 扩展都附带了供自身使用但未向用户文档化的库。可以浏览所有 Tcl 库,查找有用但未文档化的实用工具。
3.
检查 Expect 版本
:检查当前运行的 Expect 版本,尝试找到更新的版本,并验证自己的脚本是否仍然可以正常工作。
4.
比较字符串匹配和正则表达式
:使用
time
命令比较
string match
和
regexp
在执行类似任务时的性能。观察这种性能差异是否反映在
expect
命令的
-gl
和
-re
标志中。
5.
比较
sleep
命令
:使用
time
命令测量 UNIX
sleep
命令和内置
sleep
命令的执行时间。确定在什么情况下两者的差异可以忽略不计,并考虑在不同性能的机器上这种差异会如何变化。
6.
复制
date
命令输出
:找出能完全复制 UNIX
date
命令默认输出的
timestamp
格式。使用
time
命令比较
date
和
timestamp
的执行时间。
7.
编写日期转换函数
:虽然 Expect 没有将字符串转换为整数表示日期的过程,但可以思考为什么没有这样的过程,并尝试自己编写一个。
命令和变量附录
以下是 Expect 内置的命令、标志和变量列表(不包括 Tcl 提供的),该列表仅作为提示,不展示每个命令或标志的完整语法。所有不以 “exp” 开头的命令也可以使用 “exp_” 前缀调用。
命令和标志
| 命令/标志 | 描述 | 页码 |
|---|---|---|
close
| 关闭生成的进程 | 101 |
-i
| 识别进程 | 249 |
-slave
| 关闭进程的从设备 | 295 |
debug
| 控制调试器 | 410 |
-now
| 立即启动调试器 | 411 |
0 or 1
| 停止或启动调试器 | 410 |
disconnect
| 从 tty 断开进程 | 375 |
exit
| 退出 | 35 |
onexit
| 声明退出处理程序 | 321 |
-noexit
| 调用退出处理程序但不退出 | 321 |
#
| 以该值退出 | 35 |
exp_continue
|
继续
expect
命令
| 145 |
-continue_timer
| 不重启内部计时器 | 146 |
exp_internal
| 控制内部诊断 | 166 |
-info
| 返回状态 | 182 |
-f
| 将诊断信息定向到文件 | 173 |
0 or 1
| 停止或启动诊断 | 171 |
exp_open
| 以文件标识符打开生成的进程 | 304 |
-i
| 识别进程 | 304 |
-leaveopen
| 使生成的进程可访问 | 305 |
exp - pid
| 返回进程 ID | 304 |
-i
| 识别进程 | 304 |
exp_version
| 返回 Expect 的版本 | 528 |
-exit
| 如果版本不匹配则退出 | 528 |
version
| 脚本所需的版本 | 528 |
expect
| 等待模式匹配 | 72 |
timeout
| 匹配超时 | 94 |
eof
| 匹配文件结束符 | 98 |
full_buffer
| 匹配完整缓冲区 | 151 |
default
| 匹配超时或文件结束符 | 101 |
null
| 匹配空 | 155 |
-brace
| 参数用花括号括起来 | 160 |
-gl
| 通配符模式 | 109 |
-re
| 正则表达式 | 109 |
-ex
| 精确字符串 | 134 |
-notransfer
| 不更新内部缓冲区 | 154 |
-nocase
| 将输入视为全小写 | 139 |
-i
| 识别进程 | 247 |
-indices
| 保存匹配的索引 | 124 |
pattern action
| 模式 - 动作对 | 75 |
expect_after
| 在其他模式之前等待模式匹配 | 259 |
-all
|
与
expect
相同的标志,另外返回所有生成 ID 的模式
| 266 |
-info
| 返回模式 | 266 |
-no indirect
| 不返回间接模式 | 269 |
expect_background
| 在后台等待模式匹配 | 446 |
expect_before
| 在其他模式之后等待模式匹配 | 259 |
expect_user
| 等待用户输入的模式匹配 | 192 |
fork
| 创建子进程 | 374 |
inter_return
| 从调用者返回 | 230 |
interact
| 将进程的控制权交给用户 | 82 |
eof
| 匹配文件结束符 | 342 |
timeout
| 匹配超时 | 343 |
null
| 匹配空 | 343 |
-brace
| 参数用花括号括起来 | 324 |
-re
| 正则表达式 | 328 |
-ex
| 精确字符串 | 327 |
-input
| 输入进程 | 353 |
-output
| 输出进程 | 353 |
-u
| 替代用户进程 | 350 |
-o
| 处理模式 | 328 |
-i
| 识别进程 | 349 |
-echo
| 回显 | 333 |
-nobuffer
| 不缓冲部分匹配 | 337 |
-indices
| 保存匹配的索引 | 328 |
-reset
| 重置终端模式 | 344 |
-iwrite
| 保存进程生成 ID | 359 |
pattern action
| 模式 - 动作对 | 324 |
interpreter
| 将 Expect 的控制权交给用户 | 225 |
log_file
| 控制输出保存到文件 | 180 |
-a
| 保存所有输出 | 181 |
-noappend
| 保存到文件开头 | 180 |
-info
| 返回状态 | 182 |
-leaveopen
| 将文件视为日志 | 180 |
-open
| 将文件视为日志 | 180 |
file
| 保存输出到文件 | 180 |
log_user
| 控制输出到屏幕 | 175 |
-info
| 返回状态 | 182 |
0 or 1
| 停止或启动输出 | 175 |
match_max
| 控制匹配缓冲区大小 | 150 |
-d
| 默认(未来进程) | 150 |
-i
| 识别进程 | 249 |
#
| 新的缓冲区大小 | 150 |
parity
| 控制奇偶校验 | 157 |
-d
| 默认(未来进程) | 157 |
-i
| 识别进程 | 249 |
0 or 1
| 去除或保留奇偶校验 | 157 |
prompt1
| 完整命令后的提示 | 228 |
prompt2
| 不完整命令后的提示 | 228 |
remove_nulls
| 控制空字符 | 155 |
-d
| 默认(未来进程) | 155 |
-i
| 识别进程 | 249 |
0 or 1
| 保留或去除空字符 | 155 |
send
| 发送字符串 | 71 |
-
| 将下一个字符串视为字面量 | 282 |
-i
| 识别进程 | 247 |
-h
| 以人性化方式发送 | 278 |
-s
| 缓慢发送 | 275 |
-null
| 发送空字符 | 281 |
-raw
| 不插入回车符 | 198 |
-break
| 发送中断信号 | 281 |
string
| 要发送的字符串 | 71 |
send_error
| 发送到标准错误输出 | 187 |
(与
send
相同的标志)
| ||
send_log
| 发送到日志 | 182 |
-
| 将下一个字符串视为字面量 | 282 |
string
| 要发送的字符串 | 182 |
send_tty
|
发送到
/dev/tty
| 210 |
(与
send
相同的标志)
| ||
send_user
| 发送到标准输出 | 185 |
(与
send
相同的标志)
| ||
sleep #
| 休眠指定的秒数 | 196 |
spawn
| 启动一个进程 | 78 |
-console
| 将进程视为控制台 | 300 |
-ignore
| 忽略信号 | 310 |
-leaveopen
| 将文件视为生成的进程 | 289 |
-open
| 将文件视为生成的进程 | 289 |
-noecho
| 不回显生成命令 | 298 |
-nottycopy
|
不将伪终端初始化为
/dev/tty
一样
| 300 |
-nottyinit
| 不进行合理的初始化 | 300 |
-pty
| 分配伪终端但不启动进程 | 293 |
strace
| 跟踪语句 | 405 |
-info
| 返回状态 | 405 |
#
| 跟踪的深度 | 405 |
stty
| 修改终端参数 | 197 |
<ttyname
| 要修改的终端 | 204 |
raw
| 原始模式 | 198 |
-raw
| 原始模式 | 199 |
cooked
| 煮熟模式 | 205 |
-cooked
| 煮熟模式 | 205 |
echo
| 回显 | 199 |
-echo
| 无回显 | 199 |
rows [#]
| 设置或返回行数 | 205 |
columns [#]
| 设置或返回列数 | 205 |
local - stty - args
|
执行原生
stty
命令
| 205 |
system local - cmd
| 执行 Bourne shell 命令 | 207 |
timestamp
| 返回时间戳 | 528 |
-format
| 时间戳格式 | 528 |
-seconds
| 时间源 | 528 |
trap
| 定义信号处理程序 | 307 |
-code
| 要返回的代码 | 320 |
-interp
| 要使用的解释器 | 321 |
-name
| 返回当前信号的名称 | 309 |
-number
| 返回当前信号的编号 | 309 |
-max
| 返回信号的数量 | 318 |
action signal - list
| 信号列表触发的动作 | 309 |
wait
| 等待进程结束 | 105 |
-i
| 识别进程 | 249 |
-nowait
| 未来等待进程 | 106 |
变量
| 变量 | 描述 |
|---|---|
any_spawn_id
| 任何列出的生成进程 |
argc
| 初始参数的数量 |
argv
| 初始参数 |
argv0
| 脚本的名称 |
dbg
| 调试器断点正则表达式结果 |
error_spawn_id
| 标准错误的生成 ID |
exp_library
| 与平台无关的实用脚本 |
exp_exec_library
| 与平台相关的实用脚本 |
expect_out
|
expect
的结果
|
interact_out
|
interact
的结果
|
send_human
|
send - h
的控制参数
|
send_slow
|
send - s
的控制参数
|
spawn_out
|
spawn
的结果
|
spawn_id
| 当前生成的进程 |
stty_init
|
spawn
的终端参数
|
timeout
|
expect
等待的最大时间
|
tty_spawn_id
|
/dev/tty
的生成 ID
|
user_spawn_id
| 标准输入和输出的生成 ID |
通过掌握这些随机数生成、库的使用、版本管理、时间处理等方面的知识,以及熟悉相关的命令和变量,大家可以更高效地进行 Expect 编程,解决实际问题。希望本文能对大家有所帮助,让大家在 Expect 编程的道路上更加得心应手。
超级会员免费看
72

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



