経緯
以前、pexpectでWindowsのコマンドの自動化みたいなのをやろうとしてpexpectのspawnが使えないことが分かり、その流れからPexpectが使えないと思い込み、結局Linuxに切り替えて開発した経験があった。 その後はLinux中心で開発していたため知らなかったのだが、Windowsでpexpectが使用できるとのことだったので、以前を振り返りながら実際にWindowsでpexpectを利用してみた。実行環境
OS:Windows10
Python Version: 3.6.1
Pexpect Version: 4.2.1
前提
なぜspawnが利用できなかったのか
そもそもspawnが利用できなかった理由は2つある。 1つはimport時の問題で、もう一つはspawnそのものにあった。import時の問題
私がpexpectを使用する際に書いていたコードは以下になる。python
import pexpect
process = pexpect.spawn("cmd")
上記ではimportはできるものの、spawnを使用できない(has no Attributeエラーが出る。)
その原因はpexpectというmoduleの__init__.pyファイルにある。
spawnはclassであり、pexpectというpackageの中のpty_spawn.py内にある。
このpty_spawnはpexpectの__init__.pyの中でsys.platform != 'win32'の時のみimportされるように書かれている。
よってWindowsではimport pexpectではspawnが利用はできないのである。
spawnの問題?
では、なぜそのようなimport方法になっているか? それはpty_spawnが名前の通りpty(擬似端末)を使用するplatformでの使用を前提に作られたものだからである。(そもそもpexpectがその前提で作られていたのかもしれない) Windowsではptyを使用することはできない。そのためpty_spawnをimportしようとするとExceptionを吐く。興味がある方はやってみてほしい。解決策
Windowsでpexpectを使用するには、pexpectのpackageの中のpopen_spawn.pyにあるPopenSpawnクラスを使用するとのこと。 pexpectのdocument[^1]に記載があるが、このクラスはpexpectのversion4.0から追加になったwindows向けのクラスとのこと。 documentのhistory等このクラスやversion周辺を読むと、既存のspawnクラスやクロスプラットフォームについても少し触れられているので、一度読んでみても良いと思われる。作成プログラム
PopenSpawnクラスを使用したコードを作成したため載せておく。 ただし、コマンドプロンプトのコマンドを実行させて結果を取得するという用件のみを満たしたコードなので、さまざまな考慮抜けがあると思われるが、そこはご了承いただきたい。 動作確認は行っており、コマンドの実行ができていることは確認済みである。test.py
import pexpect.popen_spawn as psp
class WinPexpect():
def __init__(self):
self.process = psp.PopenSpawn("cmd")
self.prompt = ">"
try:
self.process.expect(">", timeout=10)
except:
Exception("cmd expect fail")
print(self.cmd_readlines())
def cmd_readlines(self):
res = ""
res = self.process.before.decode("shift-jis", errors="ignore") + self.process.after.decode("shift-jis", errors="ignore")
self.prompt = res.splitlines()[-1]
self.prompt = self.prompt.replace("\\", "\\\\")
return res
def cmd_sendline(self, cmd, timeout=10):
self.process.sendline(cmd)
if cmd.find("cd ") > -1:
self.prompt = self.prompt[:-1] + "\\\\" + cmd.split("cd ")[1] + ">"
try:
self.process.expect(self.prompt, timeout=timeout)
return True
except:
import traceback
traceback.print_exc()
return False
if __name__ == '__main__':
wincmd = WinPexpect()
wincmd.cmd_sendline("dir")
print(wincmd.cmd_readlines())
wincmd.cmd_sendline(" cd log")
print(wincmd.cmd_readlines())