使用Python Pexpect模塊實(shí)現(xiàn)自動(dòng)化交互腳本使用心得
參考文檔:https://pexpect.readthedocs.io/en/stable/
前言
在最近的工作中,需要使用DockerFile構(gòu)建鏡像。在構(gòu)建鏡像的過程中,有一些執(zhí)行的命令是需要交互的。例如安裝tzdata
(apt install tzdata),不過在使用apt安裝時(shí),可以直接使用DEBIAN_FRONTEND=noninteractive
前綴來取消交互(至于是禁止交互還是選擇交互的默認(rèn)值,這一點(diǎn)就不太清楚了,TODO)。具體的命令行就是DEBIAN_FRONTEND=noninteractive apt install -y tzdata
。在Dockerfile中也可以使用ARG
進(jìn)行統(tǒng)一設(shè)置。不過這種前綴設(shè)置方法僅僅適用于apt(大概TODO)。還有另一種我一開始就想到的方法,也就是利用類Unix自帶的管道(pipe)功能,實(shí)現(xiàn)進(jìn)程間通信,或是將stdin文件描述符重定向?yàn)槟硞€(gè)文本或是字符串。按道理這是可行的,但是經(jīng)過我的測(cè)試,不知道為啥行不通(等待探索TODO)。
Docker鏡像中需要構(gòu)建一個(gè)rust環(huán)境,因此需要安裝rust。安裝rust的方法一般有兩種
- 使用官方推薦的
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
命令進(jìn)行操作,這個(gè)命令首先下載腳本,然后將輸出的腳本通過管道作為sh進(jìn)程的輸入(pipe,fork,exec,dup)。sh執(zhí)行腳本的過程中會(huì)遇到一些交互,如果這時(shí)候?qū)h的stdin重定向到預(yù)定好的文件或是字符串,按道理是可以直接進(jìn)行自動(dòng)化交互的,至于為啥沒能成功。。咱也不知道(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) < in.in
為啥不能成功捏。 - 直接使用apt install rust-all。這種方法確實(shí)方便,也沒有任何交互,但是很多配置因?yàn)橛捎诤蛂ust官方可能不一樣,很多環(huán)境變量沒有設(shè)置($CARGO_HOME),有些時(shí)候還需要自己配置,屬實(shí)是麻煩得很。
因此我迫切需要一個(gè)可以自動(dòng)化交互的方法。在網(wǎng)上找了很久答案后,發(fā)現(xiàn)pexpect可以實(shí)現(xiàn)這種自動(dòng)化交互。因此在這里需要學(xué)習(xí)pexpect的相關(guān)用法。(shell腳本中也有expect相關(guān)概念,但是由于shell腳本我用起來感覺有點(diǎn)不太適應(yīng),因此就用python了)。
Pexpect簡(jiǎn)介
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.
一個(gè)中心:自動(dòng)化,各種需要交互,需要輸入都可以自動(dòng)化。
安裝
使用pip包管理進(jìn)行安裝
pip install pexpect -i https://pypi.tuna.tsinghua.edu.cn/simple/
在這里用清華源進(jìn)行加速。
This version of Pexpect requires Python 3.3 or above, or Python 2.7.
以下的流程均在Ubuntu上進(jìn)行,Windows等系統(tǒng)使用pexpect請(qǐng)參考:https://pexpect.readthedocs.io/en/stable/overview.html#windows
基本操作
在py腳本中,定義想要匹配的提示,然后進(jìn)行對(duì)應(yīng)的輸出。其中匹配可以是字符串完全匹配也可以是正則表達(dá)式狀態(tài)機(jī)匹配。
- 通過pexpect.spawn方法進(jìn)行腳本的執(zhí)行
- 配置expect,從而捕獲匹配的字符串
- 配置對(duì)應(yīng)expect的響應(yīng)
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('noah@example.com')
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')
注意事項(xiàng)
-
驚天巨坑
由于匹配的字符串可以是正則表達(dá)式也可以是普通字符串,因此有些符號(hào)是需要轉(zhuǎn)義的。
比如我下面這個(gè)腳本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\)")
這句代碼沒有使用轉(zhuǎn)義符號(hào),那么就將卡死,這玩意卡了我半小時(shí),屬實(shí)是折磨了。驚天巨坑
-
當(dāng)命令行中使用(>>,<<,|)等符號(hào)的時(shí)候,直接spwan+命令行是不起作用的,需要額外調(diào)用shell進(jìn)行操作
child = pexpect.spawn('bash -c \'curl --proto \'=https\' --tlsv1.2 -sSf https://sh.rustup.rs | sh\'')
對(duì)應(yīng):
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.
正如文檔中所說,不管怎么樣,盡量都整一個(gè)
expect(pexpect.EOF)
來把EOF給吞掉
探索
有個(gè)問題,expect是順序執(zhí)行的還是隨機(jī)匹配的呢?
可以使用一個(gè)沒有安裝rust的機(jī)器進(jìn)行測(cè)試。沒有安裝rust的機(jī)器不會(huì)出現(xiàn)Continue\? \(y\/N\)
這一提示。查看expect是否會(huì)跳過。實(shí)測(cè)不會(huì)跳過,會(huì)卡住。
經(jīng)過文檔的查看,使用
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的操作,輸入多個(gè)可匹配字符串,進(jìn)行匹配,遇到哪個(gè)就執(zhí)行哪個(gè)。
所以對(duì)于無序亂序輸出的程序,可以使用一個(gè)循環(huán),然后任意匹配??梢宰远x一個(gè)狀態(tài)機(jī),通過輸入的順序決定執(zhí)行的流程。
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就可能是終止這個(gè)狀態(tài)機(jī),TIMEOUT也有可能會(huì)中止?fàn)顟B(tài)機(jī)。
最終Rust自動(dòng)化無交互安裝腳本如下
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了,可以適當(dāng)將timeout變大一些,畢竟下載安裝rust還是需要一定時(shí)間的
碎碎念
在我尋找答案的過程中,還發(fā)現(xiàn)了這么一個(gè)答案文章來源:http://www.zghlxwxcb.cn/news/detail-424973.html
curl https://sh.rustup.rs -sSf | sh -s -- -y
使用這個(gè)方法可以直接無交互自動(dòng)化安裝,但是這是一個(gè)特解。應(yīng)用程序千千萬萬,如果每個(gè)應(yīng)用程序都有特殊的無交互方式,那么需要了解所有的相關(guān)操作,這不免有點(diǎn)太累了。好處呢,就是這個(gè)方案基本上是永久有效,能夠跟著應(yīng)用程序更新。壞處呢也就是這只是一個(gè)特解,pexpect這個(gè)通解還是需要的。文章來源地址http://www.zghlxwxcb.cn/news/detail-424973.html
到了這里,關(guān)于[Python自動(dòng)化]使用Python Pexpect模塊實(shí)現(xiàn)自動(dòng)化交互腳本使用心得的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!