対話処理を自動化したい(pexpect
)
1import pexpect
2
3child = pexpect.spawn("コマンド")
4child.expect("待ち文字列")
5child.sendline("別のコマンド")
6child.expect("待ち文字列")
7child.terminate()
pexpect
はexpectをPythonで実行できるようにしたツールです。
リモートサーバーとRsyncやSSHなどするときに必要なパスワード入力などの
対話処理を含んだ手順を自動化できます。
Pure Pythonで書かれており、 expectやTcl、その他のC言語モジュールを必要としません。 また、文字のエスケープや改行処理もうまく扱ってくれるため 素のexpectより読み書きしやすいと思います。
コマンド送信したい(sendline
)
1child.sendline("コマンド")
sendline
でコマンドを送信できます。
CRLFの改行コード(\r\n
)付きで送信してくれるので、
コマンドを記入するだけでよいです。
処理待ちしたい(expect
)
1# str型
2child.expect("文字列")
3
4# list[str]型
5child.expect(["文字列1", "文字列2"])
6# =0: 文字列1にマッチ
7# =1: 文字列2にマッチ
expect
に処理が終わった時に表示される文字列を指定することで、
直前のコマンドの処理を待つことができます。
引数は、str
型とlist[str]
型で指定でき、正規表現も使えます。
返り値はマッチした文字列のインデックスになっています。
プロンプト待ちしたい
1PROMPT = ["\\$", "\\#", pexpect.EOF]
2child.sendline("コマンド")
3child.expect(PROMPT)
標準的なサーバー設定のプロンプト表示は、$
が一般ユーザー、#
が管理者ユーザーとなっています。
$
もしくは#
を待つことでサーバー内での作業を続けることができます。
パスワード待ちしたい
1# 接続成功したとき
2ok = ["password:"]
3
4# 接続失敗したとき
5ng = ["Connection refused", "Timed out"]
6connected = child.expect([*ok, *ng])
7# =0: "password:" にマッチ
8# =1: "Connection refused" にマッチ
9# =2: "Timed out" にマッチ
10
11# 接続失敗
12if connected > 0:
13 child.terminate(force=True)
14 sys.exit()
15
16# 接続成功
17child.sendline("パスワード")
rsyncやSSH接続ではパスワード入力を要求されます。
多くの場合password:
の文字列が表示されるため、.expect
することで接続確認ができます。
また、接続できなかったときの文字列を確認することで、例外として処理できます。
上のサンプルではok
とng
のリストを定義し、
マッチしたときに返ってくる文字列のインデックスを利用して
簡単な成功/失敗の判断をしています。
パスワード送信したい(sendline
)
1import os
2from dotenv import load_dotenv
3
4# 環境変数(.env)を読み込む
5load_dotenv()
6password = os.environ.get("PASSWORD")
7
8child.sendline(password)
利用することが多いパスワード送信部分のサンプルです。
パスワードのベタ書きを避けるため、
.env
に環境変数として定義しdotenv
モジュールで読み込んでいます。
# プロジェクト/.env
PASSWORD=パスワード
.env
ファイルは、プロジェクトルートに作成し、必要な環境変数を定義しておきます。
注意
パスワード情報などが含まれている.env
は
Gitリポジトリに追加しないように気をつけてください。
うっかりコミットしないように.gitignore
に追記しておきます。
rsyncしたい
$ rsync -auvz SRC_REMOTE DEST_LOCAL
ユーザー名@ホスト名's password: 🔑(ログインパスワードを入力)
receiving file list ... done
同期したファイル名...
sent XX bytes received XXXX bytes XX.XX bytes/sec
total size is XXXXXXXXXX speedup is XXXXXX.XX
rsyncで
リモートサーバーからファイルを取得したときの表示される内容のサンプルです。
これを再現するようにpexpect
で手順を並べていきます。
1import pexpect
2
3# rsyncを開始
4child = pexpect.spawn("rsync -auvz SRC_REMOTE DEST_LOCAL")
5child.expect("password:") # パスワード認証を確認
6child.sendline("パスワード") # パスワードを送信
7child.expect(pexpect.EOF) # 処理が終わることを確認
8result = child.before.decode("utf-8") # 結果を取得
9print(result)
password:
の文字列が表示されたあとに、パスワードを送信しています。
pexpect.EOF
を.expect
することで、
ファイル転送の処理の完了を待つことができます。
実行した結果はbefore
にバイナリ文字列で保存されています。
テキストデータが欲しいのでutf-8
でデコードしています。
注釈
child.expect("文字列")
で待っていた文字列とマッチした場合、
child.before
とchild.after
にマッチした前後の情報がバイナリ文字列で保存されています。
なので、child.before
で実行結果が確認できます。
SSH接続したい(pexpect.pxssh
)
$ ssh ユーザー名@ホスト名
ユーザー名@ホスト名's password: (パスワードを入力)
Last login: 日時 from IPアドレス
[ユーザー名@ホスト名 ~]$ uptime
21:41 up 6 days, 11:26, 4 users, load averages: 2.30 3.65 4.16
[ユーザー名@ホスト名 ~]$ df -h
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
...
[ユーザー名@ホスト名 ~]$ exit
logout
SSH接続して、いくつかのコマンドを実行したときに内容のサンプルです。
この手順をpexpect.pxssh
モジュールを使って並べます。
1from pexpect import pxssh
2
3ssh = pxssh.pxssh(encoding="utf-8")
4ssh.login(hostname, username, password)
5
6ssh.sendline("uptime")
7ssh.prompt()
8print(ssh.before)
9
10ssh.sendline("df -h")
11ssh.prompt()
12print(ssh.before)
13
14ssh.logout()
pexpect.pxssh
モジュールで簡単にSSH接続できます。
pexpect.spawn
で書いた場合と比べると、
ログイン処理(.login
)とプロンプト待ち(.prompt
)が簡単に書けるようになっています。
1import pexpect
2
3# 待機するプロンプト表示
4PROMPT = ["\\$", "\\#", pexpect.EOF]
5
6# SSH接続を開始する
7child = pexpect.spawn("ssh ユーザー名@ホスト名")
8child.expect("password:")
9
10# パスワードを送信してログインする
11child.sendline("パスワード")
12child.expect(PROMPT)
13
14# コマンド(uptime)を実行
15child.sendline("uptime")
16child.expect(PROMPT)
17print(child.before.decode("utf-8"))
18
19# コマンド(df -h)を実行
20child.sendline("df -h")
21child.expect(PROMPT)
22print(child.before.decode("utf-8"))
23
24# コマンド(exit)を実行
25child.sendline("exit")
26child.expect(PROMPT)
27print(child.before.decode("utf-8"))
28
29child.terminate()