背景
老规矩还是先写背景,我为什么要起这个标题,是因为我就是用“shell向终端输入指令”作为关键词来检索教程的,结果肯定是没有任何发现,因为实际搜索的应该是括号内的内容。
这个项目需要自动化复现客户现场的一个问题,通过某个测试脚本来不停地执行上下电操作,但是问题是,这个测试脚本需要从终端输出一些命令,他再把对应的命令发下去,例如下面这个界面:
他要求我输出t或者f,来选择模式,这个时候不管你在shell脚本里echo,还是send,都不会有任何响应,我个人的理解是这些数据被输入到终端去了,而不是输入到了我们需要的这个测试程序中
(当然这只是我个人的理解,有懂的大佬可以在下面指正我)
(但是我的博客浏览量很低,你们大概率是看不到大佬在下面指正我了)
直接上代码
#!/usr/bin/expect
set timeout 60
spawn ./power_control_test_0718 eno1
expect "f: Soem Firmware Update" {send "t\r"}
expect "card_pulltime default : 200" {send "1000\r"}
sleep 2
# choose card: 1,2,3
expect "please input slave_n:" {send "1\r"}
expect "r: begin cyclic reading" {send "c\r"}
# choose chanel: 1,3,4
expect "such as: 1" {send "1\r"}
# default: 1
expect "such as: 1" {send "1\r"}
expect "r: begin cyclic reading" {send "r\r"}
expect "such as: 300000 1000" {send "300000 100\r"}
puts "---Card1:Now get voltage 100 times."
expect "r: begin cyclic reading" {send "\0\x3\r"}
puts "---Card1:Now exit."
exit
1. 头文件
这里一定要用
#!/usr/bin/expect
而不是
#!/bin/bash
这直接决定了你这个脚本的解释器是什么,我这里使用的是纯粹的expect语法,那么这两种有什么区别呢?区别就是:
shell的打印命令是 echo
expect的打印命令是 puts
shell的赋值命令是 a=1
expect的赋值命令是 set a 1
这只是一个简单的例子,具体区别可以找其他大佬的博文研究一下,其实艺高人胆大的程序猿可以选择混写(就像Python脚本里写shell操作一样),但是我不会,这里就不展开了
2. 设置超时等待时间
set timeout 60 # 设置超时等待时间为60秒
set timeout -1 # 设置永不超时,死等
这个很好理解吧,默认超时时间是10秒,一般这种脚本都是循环起来整夜整夜跑的,尽量不要设置永不超时,不然很容易把环境挂住
3. spawn(跟踪)
spawn ./power_control_test_0718 eno1
spawn后一般跟一个Linux执行命令,表示开启一个会话,启动进程,并跟踪后续交互信息,所以问题就在这里!
我在网口(eno1)上执行了我的测试脚本(power_control_test_0718),一般shell脚本内执行了这一句之后就接着发送其他命令了,而不是“进入”这个测试程序,继续跟踪做输入交互
4. expect/send(捕捉/发送)
expect "f: Soem Firmware Update" {send "t\r"} # 第一次选择t模式
expect "card_pulltime default : 200" {send "1000\r"}expect "f: Soem Firmware Update" {send "f\r"} #第二次选择f模式
所以现在你可以再看一下我开头发的,第一句的意思就是当检测到"f: Soem Firmware Update"的时候,向屏幕发送"t"
【注】这个\r表示敲击回车,有些程序你输入后就自动跳转了就没有必要敲回车,然鹅有些程序也不要你输入任何东西,你直接敲回车就表示选择默认配置了,自行调整即可
还有一点值得注意的是,这一段程序可以像Python里面的match-case一样进行分支选择
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
所以上面的那三句话也可以写成
expect {
"f: Soem Firmware Update" {send "t\r"};exp_continue;}
expect "card_pulltime default : 200" {send "1000\r"}
}
exp_continue紧跟在某个判断分支的后面,表示匹配完分支1之后还可以匹配分支2,分支3…
但是你们有没有发现少了一句,因为这个确实和分支选择类似,你走到了这个分支,只能有一个操作,如果你想在第一次选板卡的时候选板卡1,第二次同样选择的时候选择板卡2,那你只能采用第一种写法,而不是后面这种
5. exit/expect eof/interact(退出)
- exit:退出expect脚本
- expect eof:spawn进程结束后会向expect发送eof,接收到eof代表该进程结束
- interact :执行完代码后保持交互状态,将控制权交给用户。没有该命令的话,执行完后自动退出而不是留在远程终端上
【例】我们以一个远程登录为例,PC1以aaa用户权限执行expect脚本,功能是切换到root模式后登录PC2的bbb账户,脚本执行结束,选择以下三种方式退出
① exit
仍在PC1上,为aaa用户
② expect eof
仍在PC1上,为aaa用户
③ interact
在PC2上,可以使用bbb用户操作
【注】interact后的命令不起作用,比如interact后再执行exit,并不会退出root用户
【附】shell和expect混写
最后附加一段,shell和expect混写的代码,有兴趣的同学可以去参考Linux学习之expect操作详解
#!/bin/bash
user="mrswhite"
host="192.168.37.9"
password="test20221007"
/usr/bin/expect << EOF
set time 20
spawn ssh $user@$host
expect {
"*yes/no" { send "yes\r"; exp_continue }
"*password:" { send "$password\r" }
}
expect "*#"
send "pwd\r"
expect "*#"
send "df -h\r"
expect "*#"
send "exit\r"
interact
expect eof
EOF
总结
expect是一个很强大的命令,但我只是临时定位问题用到了一下,感觉编程逻辑不是很复杂,和shell基本类似,但是语法是真的太恶心了,if,while,for,包括条件比较都必须严格遵循规定,不如Python简洁,建议大家多用Python