Help us understand the problem. What is going on with this article?

Windowsでpexpectを利用する

More than 1 year has passed since last update.

経緯

以前、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のdocument1に記載があるが、このクラスは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())

反省点

windowsでpexpectが使えないと判断したのは、ほぼほぼ私がしっかりソースコードやdocumentを読まなかったことに原因があるといってもいい。
これに関しては今後も似たような判断をしないとは言えないため、細心の注意を払ってプログラミングを行っていきたい。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away