24、Expect 编程中的杂项知识与实用技巧

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 编程的道路上更加得心应手。

需求响应动态冰蓄冷系统需求响应策略的优化研究(Matlab代码实现)内容概要:本文围绕需求响应动态冰蓄冷系统及其优化策略展开研究,结合Matlab代码实现,探讨了在电力需求侧管理背景下,冰蓄冷系统如何通过优化运行策略参需求响应,以实现削峰填谷、降低用电成本和提升能源利用效率的目标。研究内容包括系统建模、负荷预测、优化算法设计(如智能优化算法)以及多场景仿真验证,重点分析不同需求响应机制下系统的经济性和运行特性,并通过Matlab编程实现模型求解结果可视化,为实际工程应用提供理论支持和技术路径。; 适合人群:具备一定电力系统、能源工程或自动化背景的研究生、科研人员及从事综合能源系统优化工作的工程师;熟悉Matlab编程且对需求响应、储能优化等领域感兴趣的技术人员。; 使用场景及目标:①用于高校科研中关于冰蓄冷系统需求响应协同优化的课题研究;②支撑企业开展楼宇能源管理系统、智慧园区调度平台的设计仿真;③为政策制定者评估需求响应措施的有效性提供量化分析工具。; 阅读建议:建议读者结合文中Matlab代码逐段理解模型构建算法实现过程,重点关注目标函数设定、约束条件处理及优化结果分析部分,同时可拓展应用其他智能算法进行对比实验,加深对系统优化机制的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值