使用Python Pexpect模塊實作自動化互動腳本使用心得
參考檔案:https://pexpect.readthedocs.io/en/stable/
前言
在最近的作業中,需要使用DockerFile構建鏡像,在構建鏡像的程序中,有一些執行的命令是需要互動的,例如安裝tzdata(apt install tzdata),不過在使用apt安裝時,可以直接使用DEBIAN_FRONTEND=noninteractive 前綴來取消互動(至于是禁止互動還是選擇互動的默認值,這一點就不太清楚了,TODO),具體的命令列就是DEBIAN_FRONTEND=noninteractive apt install -y tzdata,在Dockerfile中也可以使用ARG進行統一設定,不過這種前綴設定方法僅僅適用于apt(大概TODO),還有另一種我一開始就想到的方法,也就是利用類Unix自帶的管道(pipe)功能,實作行程間通信,或是將stdin檔案描述符重定向為某個文本或是字串,按道理這是可行的,但是經過我的測驗,不知道為啥行不通(等待探索TODO),
Docker鏡像中需要構建一個rust環境,因此需要安裝rust,安裝rust的方法一般有兩種
- 使用官方推薦的
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh命令進行操作,這個命令首先下載腳本,然后將輸出的腳本通過管道作為sh行程的輸入(pipe,fork,exec,dup),sh執行腳本的程序中會遇到一些互動,如果這時候將sh的stdin重定向到預定好的檔案或是字串,按道理是可以直接進行自動化互動的,至于為啥沒能成功,,咱也不知道(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) < in.in為啥不能成功捏, - 直接使用apt install rust-all,這種方法確實方便,也沒有任何互動,但是很多配置因為由于和rust官方可能不一樣,很多環境變數沒有設定($CARGO_HOME),有些時候還需要自己配置,屬實是麻煩得很,
因此我迫切需要一個可以自動化互動的方法,在網上找了很久答案后,發現pexpect可以實作這種自動化互動,因此在這里需要學習pexpect的相關用法,(shell腳本中也有expect相關概念,但是由于shell腳本我用起來感覺有點不太適應,因此就用python了),
Pexpect簡介
Pexpect allows your script to spawn a child application and control it as if a human were typing commands.
Pexpect can be used for automating interactive applications such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup scripts for duplicating software package installations on different servers. It can be used for automated software testing.
一個中心:自動化,各種需要互動,需要輸入都可以自動化,
安裝
使用pip包管理進行安裝
pip install pexpect -i https://pypi.tuna.tsinghua.edu.cn/simple/
在這里用清華源進行加速,
This version of Pexpect requires Python 3.3 or above, or Python 2.7.
以下的流程均在Ubuntu上進行,Windows等系統使用pexpect請參考:https://pexpect.readthedocs.io/en/stable/overview.html#windows
基本操作
在py腳本中,定義想要匹配的提示,然后進行對應的輸出,其中匹配可以是字串完全匹配也可以是正則運算式狀態機匹配,
- 通過pexpect.spawn方法進行腳本的執行
- 配置expect,從而捕獲匹配的字串
- 配置對應expect的回應
example
# This connects to the openbsd ftp site and
# downloads the recursive directory listing.
import pexpect
child = pexpect.spawn('ftp ftp.openbsd.org')
child.expect('Name .*: ')
child.sendline('anonymous')
child.expect('Password:')
child.sendline('[email protected]')
child.expect('ftp> ')
child.sendline('lcd /tmp')
child.expect('ftp> ')
child.sendline('cd pub/OpenBSD')
child.expect('ftp> ')
child.sendline('get README')
child.expect('ftp> ')
child.sendline('bye')
注意事項
-
驚天巨坑
由于匹配的字串可以是正則運算式也可以是普通字串,因此有些符號是需要轉義的,
比如我下面這個腳本import pexpect import sys child = pexpect.spawn('bash -c \'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh\'') child.expect("Continue\? \(y\/N\)") print("get 1") child.sendline("y") child.expect('>') print("get 2") child.sendline('1') child.expect(pexpect.EOF)如果
child.expect("Continue\? \(y\/N\)")這句代碼沒有使用轉義符號,那么就將卡死,這玩意卡了我半小時,屬實是折磨了,驚天巨坑
-
當命令列中使用(>>,<<,|)等符號的時候,直接spwan+命令列是不起作用的,需要額外呼叫shell進行操作
child = pexpect.spawn('bash -c \'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh\'')對應:
curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh -
If you wish to read up to the end of the child’s output without generating an EOF exception then use the expect(pexpect.EOF) method.
正如檔案中所說,不管怎么樣,盡量都整一個
expect(pexpect.EOF)來把EOF給吞掉
探索
有個問題,expect是順序執行的還是隨機匹配的呢?
可以使用一個沒有安裝rust的機器進行測驗,沒有安裝rust的機器不會出現Continue\? \(y\/N\)這一提示,查看expect是否會跳過,實測不會跳過,會卡住,
經過檔案的查看,使用
index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
do_something()
elif index == 1:
do_something_else()
elif index == 2:
do_some_other_thing()
elif index == 3:
do_something_completely_different()
index的操作,輸入多個可匹配字串,進行匹配,遇到哪個就執行哪個,
所以對于無序亂序輸出的程式,可以使用一個回圈,然后任意匹配,可以自定義一個狀態機,通過輸入的順序決定執行的流程,
try:
index = p.expect(['good', 'bad'])
if index == 0:
do_something()
elif index == 1:
do_something_else()
except EOF:
do_some_other_thing()
except TIMEOUT:
do_something_completely_different()
在上面這段代碼中,EOF就可能是終止這個狀態機,TIMEOUT也有可能會中止狀態機,
最終Rust自動化無互動安裝腳本如下
import pexpect
child = pexpect.spawn('bash -c \'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh\'')
try:
while True:
index = child.expect(['Continue\? \(y\/N\)','>'])
if index == 0:
child.sendline('y')
print("Continue? (y/N) y")
elif index == 1:
child.sendline('1')
print("> 1")
except pexpect.EOF:
exit
except pexpect.TIMEOUT:
print("timeout")
如果timeout了,可以適當將timeout變大一些,畢竟下載安裝rust還是需要一定時間的
碎碎念
在我尋找答案的程序中,還發現了這么一個答案
curl https://sh.rustup.rs -sSf | sh -s -- -y
使用這個方法可以直接無互動自動化安裝,但是這是一個特解,應用程式千千萬萬,如果每個應用程式都有特殊的無互動方式,那么需要了解所有的相關操作,這不免有點太累了,好處呢,就是這個方案基本上是永久有效,能夠跟著應用程式更新,壞處呢也就是這只是一個特解,pexpect這個通解還是需要的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551168.html
標籤:其他
下一篇:返回列表
