• 命令行交互性三个级别及其自动化解决方案


    目录

    级别一

    级别二

    级别三

    尽量减少交互难度


    级别一

    固定的一个或者几个prompt。对策:定义prompt,执行命令后等待prompt的出现。可以使用正则表达式来匹配prompt。比如用于匹配linux prompt的正则表达式可以是#|\\$或者(@.*#)|(@.*\\$)。

    级别二

    以固定的方式进行交互。比如执行命令后,出现“password:”时,输入密码,出现“y/n”时,输入y表示同意继续。

    对策:1、按顺序wait指定的内容,可以是正则匹配,然后输入相应内容。2、扩展描述prompt的正则表达式,包含所有可能出现的标志性提示,按顺序输入相应内容。

    例如:

    测试需求:

    SSL VPN tunnel测试时,需要在互联网终端上运行SSL VPN的客户端工具来建立隧道,然后在这个互联网终端上ping ssl vpn站点内部的vm。

    交互需求:

    SSH登录互联网终端,sudo su之后运行SSL VPN的客户端,sudo su之后,出现password for user:时要输入密码。

    运行SSL VPN的客户端后,当输出中出现“Password for VPN:”时要,输入vpn密码,当输出中出现“(Y/N)”时,输入“y”,当输出中出现“STATUS::Tunnel running”,说明隧道建立完成,可以进行后续操作了。因为这个程序在前台一直运行着,所以不会出现prompt。

    解决办法(使用robot示意代码):

    这里提前封装了关键字Send Command,用于在ssh连接上执行一条命令,参数regexp指明该关键字中read until regexp中等待的模式。

    方式一:wait指定的内容

    set_client_configuration       prompt=REGEXP:#|\\$

    Send Command     sudo su    regexp=password\\sfor\\suser:

    Send Command     ${internet_client_password}​​    

    Send Command     /home/sslvpnclient --server ${​​f1}​​[floating_ip_address]:${​​service_port}​​ --vpnuser ${​​sslvpn_username} --keepalive ​​    regexp=Password\\sfor\\sVPN:

    Send Command     ${​​sslvpn_password}​​     regexp=\\(Y/N\\)

    Send Command     y      regexp=STATUS::Tunnel\\srunning

    方式二:扩展描述prompt的正则表达式

    set_client_configuration       prompt=REGEXP:#|\\$|(Password\\sfor\\sVPN:)|\\(Y/N\\)|(STATUS::Tunnel\\srunning)|(password\\sfor\\suser:

    Send Command     sudo su

    Send Command     ${internet_client_password}​​

    Send Command

        ...    /home/sslvpnclient --server ${​​f1}​​[floating_ip_address]:${​​service_port}​​​​​​​​​ --vpnuser ${​​​​​​​​​sslvpn_username} --keepalive

    ​​Send Command     ${​​​​​​​​​sslvpn_password}​​​​​​​​​

    Send Command     y

        

    级别三

    不确定下一步将会要求输入什么,比如,可能要求输入密码,也可能要求输入y进行确定。这个时候需要根据提示来决定输入的内容。

    例如ssh登录到一台设备后,在这台设备上再通过ssh命令登录另一台设备,客户端没有key或者服务端的key更新后,登陆时会提示“yes/no?”,否者只会提示输入密码“password:”。

    TCL的expect包非常擅长处理这种场景,支持匹配列表和内置循环匹配功能。

    现在来说一下python的解决方案。

    Python有pexpect包,它支持用于匹配的列表,但是没有内置的循环匹配功能,所以需要自己写循环。

    可以不使用pexpect,我们只需要将可能的提示都扩展到描述prompt的正则表达式中。循环中写几个if分支,通过当前的特征性提示内容,决定下一步操作。代码如下:

    这里提前封装了一个函数send_command,用于在ssh连接上执行一条命令。s: SSHLibrary instance。log是logging.getLogger()返回的logger。在这里,这两个参数不是重点,大家可以忽略它们。

    1. def ssh_connect (s, log, host, port, username, password):
    2. #扩展prompt的正则表达式
    3. s.set_client_configuration(prompt="REGEXP:#|\\$|password:|\\(yes/no.*\\?")
    4. output = send_command(s,log,f'ssh -p {port} {username}@{host}')
    5. while True:
    6. if 'password:' in output:
    7. output = send_command(s,log,password)
    8. break
    9. elif 'yes/no' in output:
    10. output = send_command(s,log,'yes')
    11. #恢复prompt的正则表达式
    12. s.set_client_configuration(prompt="REGEXP:#|\\$")

    接下来我把这个功能抽象成一个函数,以达到通用的级别三交互的效果。

    函数参数说明:

    command是要执行的主命令。

    expects是两层列表。对于每一个内层列表,第一个元素是期望匹配到的正则表达式式,第二个元素是匹配到这个正则表达式后要输入的内容,第三个元素表示退出循环还是继续循环。

    1. def expect(s, log, command, expects):
    2. #为了方便阅读代码,为exp中每个index取个名字。
    3. reg, cmd, control = 0, 1, 2
    4. #扩展prompt的正则表达式
    5. prompt = s.get_connection().prompt
    6. #exp[reg]外面加圆括号是为了避免exp[reg]中本身就有|导致的结合性问题
    7. for exp in expects:
    8. prompt += "|" + "(" + exp[reg] + ")"
    9. s.set_client_configuration(prompt=prompt)
    10. output = send_command(s,log,command)
    11. break_flag=False
    12. while True:
    13. for exp in expects:
    14. if re.search(exp[reg], output):
    15. output = send_command(s, log, exp[cmd])
    16. if exp[control]=="break":
    17. break_flag=True
    18. break
    19. if break_flag:
    20. break
    21. #恢复prompt的正则表达式
    22. s.set_client_configuration(prompt="REGEXP:#|\\$")

    使用示意:

    1. s = SSHLibrary()
    2. s.open_connection("10.66.196.39",prompt=linux_prompt)
    3. s.login("root","Ionqa123!@#")
    4. log = Logger(logger="expr",filename="expr").getlog()
    5. command = f'ssh root@10.66.196.30'
    6. expects = [
    7. ["password:", "password", "break"],
    8. ["\\(yes/no.*\\?", "yes", "continue"],
    9. ]
    10. expect(s,log,command,expects)
    11. s.close_connection()

    是不是有点tcl expect的味道,我把对匹配列表的支持和循环匹配的能力都封装在了上面的expect()函数中。

    尽量减少交互难度

    为了避免麻烦,应该尽量减少困难交互场景的出现。如果执行命令时加上某个参数就可以不用额外交互,那就带上这个参数。比如执行命令时同时就输入密码,执行命令时要求不进行某种确认。比如ssh命令使用选项-o StrictHostKeyChecking=no就可以避免出现提示“continue connecting (yes/no/[fingerprint])?”。比如ssh登录时如果没有条件输入密码,则可以配置使用免密登录方式。

  • 相关阅读:
    Excel函数公式大全—HLOOKUP函数
    深入路由器交换数据传输
    为什么创建百科词条?百科营销的作用
    JVM 内存模型和结构详解 (五大模型图解)
    std::move以及右值引用等
    记录荒废了三年的四年.net开发的第一次面试
    Python 语音识别系列-实战学习之初识语音识别
    ModuleNotFoundError: No module named ‘scripts.animatediff_mm‘ 解决方案
    SpringBoot_redis使用实战(四)_消息模式
    [初学rust] 05_ rust struct
  • 原文地址:https://blog.csdn.net/jxzdsw/article/details/128055855