目錄

廣告 AD

Pexpect:自動化的神奇工具,讓自動化變得更簡單

當初看到 Pexpect 就覺得應該蠻好玩的

這次測試下來也真的蠻好用的

廣告 AD

Pexpect 讓你可以透過 Python 跟其他應用程式互動,省去了自己使用 subprocess 和處理輸入輸出的操作,只要透過 library 就可以與之互動,接下來就介紹一些基本的用法。


安裝上直接使用 pip install 就可以了。

pip install pexpect

一般的 Linux 環境下,直接使用 pexpect.spawn

import pexpect

child = pexpect.spawn('ssh -T [email protected]')

目前的版本支援 Windows,但由於 pexpect.spawn 底層使用 Linux 的 pseudoterminals,在 Windows 使用 pexpect.spawn 會出現以下的錯誤,Windows 則是可以使用 pexpect.popen_spawn.PopenSpawn 來運行。

AttributeError: module ‘pexpect’ has no attribute ‘spawn’

Pexcept Document - Pexcept on Windows

import pexpect.popen_spawn

child = pexpect.popen_spawn.PopenSpawn('ssh -T [email protected]')

我們可以用 expect 來等待應用程式回傳給定的字串,給定的字串可以使用正規表達式

並在之後透過 before 和 after 來獲取應用程式的字串

  • before: 回傳 expect 配對到的字串之前的字串
  • after: 回傳 expect 配對到的字串

下面我們用檢測是否能正常用 ssh 登入 github 的指令當作範例,正常輸出為:

Hi bigcat! You've successfully authenticated, but GitHub does not provide shell access.

我們抓取 success 的這個字串,你會看到 before 印出了 success 之前的字串,而 after 則是 success。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
child.expect("success")
print(f"Before: {child.before.decode()}")
print(f"After: {child.after.decode()}")
Before: Hi bigcat! You've 
After: success

緊接著我們繼續抓取剩餘字串,透過正規表達式,我們抓取了逗號(,)開頭,句點(.)結尾的字串,before 包含著 success 之後到逗號之前的字串,而 after 就是我們要抓取的字串。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
child.expect("success")
print(f"Before: {child.before.decode()}")
print(f"After: {child.after.decode()}")

child.expect(r",.+\.")
print(f"Before: {child.before.decode()}")
print(f"After: {child.after.decode()}")
Before: Hi bigcat! You've 
After: success
Before: fully authenticated
After: , but GitHub does not provide shell access.

如果我們要直接抓取到 EOF 的所有字串,可以直接傳入 pexpect.EOF,則 after 就會是 pexpect.EOF 的變數,before 存著所有 EOF 之前的字串,包含換行,因此輸出才會空一行。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
child.expect(pexpect.EOF)
print(f"Before: {child.before.decode()}")
print(f"After: {child.after}")
Before: Hi bigcat! You've successfully authenticated, but GitHub does not provide shell access.

After: <class 'pexpect.exceptions.EOF'>

我們也可以設定 timeout 來設定等待的最長時間,單位為秒,超過會拋出 pexpect.TIMEOUT。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
try:
    child.expect(pexpect.EOF, timeout=10)
except pexpect.TIMEOUT:
    print("Timeout")
Timeout

另外,我們也可以傳入 list,裡面是各種可能會出現的字串、正規表達式,或是 pexpect.EOFpexpect.TIMEOUT,最後看輸出是符合哪一個,回傳符合的 index,我們可以透過這個 index 來知道應用程式目前的狀態,並依據狀態做出相對的處理。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
idx = child.expect(["success", "denied", pexpect.TIMEOUT], timeout=10)
match idx:
    case 0:
        print("Login Success")
    case 1:
        print("Login Failed")
    case 2:
        print("Connection Failed")
Login Success

最後,這裡有一點要提醒,在正規表達式的使用上,如果有使用到 *+,因為 expect 會採用最小符合原則,因此只要字串有符合,則會馬上回傳,並不會看後面的字串,我們看例子比較好懂。

持續上面的例子,如果使用 expect(r".*") 則甚麼都抓不到。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
child.expect(r".*")
print(child.after.decode() == "")
True

expect 讓我們知道如何抓取應用程式的輸出,sendsendline 讓我們可以傳輸入給應用程式,達到跟應用程式互動的功能,sendline 就是多傳送換行的 send

我們一樣用之前的例子,只是如果電腦上沒有儲存 github 的 fingerprint,則會詢問使用者是否要繼續連線,這時候就要輸入 yes 來繼續連線,我們使用 sendline 來達成這個動作。但以下的範例在 Windows 上測試會有問題,建議在 Linux 上執行。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
while True:
    idx = child.expect(["success", "denied", r"fingerprint.+\?", pexpect.TIMEOUT], timeout=10)
    match idx:
        case 0:
            print("Login Success")
            break
        case 1:
            print("Login Failed")
            break
        case 2:
            print("Store fingerprint")
            child.sendline("yes")
        case 3:
            print("Connection Failed")
            break
Store fingerprint
Login Success

有時候你會想看應用程式的輸出或是為了 debug,你可以使用透過設定 logfilesys.out.buffer,這樣輸出就會顯示在 stdout 上面了。

import pexpect
import sys

child = pexpect.spawn('ssh -T [email protected]')
child.logfile = sys.stdout.buffer
child.expect("success")
Hi bigcat! You've successfully authenticated, but GitHub does not provide shell access.

也可以寫到檔案上,只要將 logfile 設定成檔案就好,記得要用 binary mode。

import pexpect

child = pexpect.spawn('ssh -T [email protected]')
child.logfile = open("output.log", "wb")
child.expect("success")
child.logfile.close()

廣告 AD