以下の内容はhttps://uepon.hatenadiary.com/entry/2025/08/08/010114より取得しました。


対話的プロセス自動化ツール|pexpectとexpectで始めるコマンド自動化入門

こんな経験ない?

  • 毎日同じサーバーにSSHでログインして、同じコマンドを打つのが面倒 ‐ CLIで標準入力かつ手動入力で操作するプログラム。入力ミスすると致命的なのでなんとかしたい。
  • バックアップスクリプトを夜中に自動実行したいが、途中でパスワードがあり入力待ちで処理が止まる

昔こんな業務に携わっていることがありました。その時にはサーバとは別の操作端末なるものがあり、WindowsTeraTermのマクロを機能を使って操作をしていました。それだけのためにWindowsPCがいるというのもイヤハヤといった時代でしたが、GUIがなくキーボード入力必須という環境、もしかしたら、まだありますかね?

古(いにしえ)の運用エンジニアでWindowsTeraTermを使っている方なら、マクロ機能でこんな作業を自動化した経験があるかもしれません😫

そんなTeraTermマクロができることを、Linux(WSL含む)やmacOSコマンドラインでもできるようにするのが、今回紹介するpexpect(Pythonexpect(Tcl)です。これらを用いることで、CLIで標準入力を行うプログラム(以降、対話的プロセスと呼びます)を全てプログラムで自動化できます。

これらのpexpectexpectの両方について紹介します。

概要

pexpect、expectとは?

両者とも、目的は対話的プロセスの自動化を目的としています。

pexpectは、Pythonで対話的プロセスを制御するライブラリです。特定の出力パターンを待機し、適切な入力を自動送信できます。 - expectは、Tclベースのコマンドラインツールで、pexpectの先祖となります。対話的自動化の標準的なツールとして長年使われています。

インストール方法

pexpectexpectのインストール方法についてみていきます。expectはDocker経由で使用することもありそうなので、Alpine Linuxについてもインストールを書いておきました。Docker Composeなどで使用する可能性もあるかもしれませんね。

🐍pexpect(Python)のインストール方法

$ pip install pexpect

🪶expect(Tcl)のインストール方法

# Ubuntu/Debian
$ sudo apt update
$ sudo apt install expect

# Alpine Linux(Docker用)
$ apk add --no-cache expect

インストールの注意点

これらについては処理系が決まっています。基本的にはexpectのほうが用途役割は広いかなと思いますが、pexpectPythonの豊富なライブラリ資源を使用することができるので、多彩なことができると思います。古いシステムではexpect、比較的新しいシステムではpexpect使うのがいいかもしれません。

  • pexpect → Unix系システム(WSL、LinuxmacOS)のみ対応。
  • expect → Unix系システムで標準的に利用可能。

Windowsネイティブの対応はwinpexpectが存在。

基本的な使い方

🐍pexpectの基本的な使用方法

基本的には以下のように使用します。

import pexpect

# 最初の例(コマンド実行)
child = pexpect.spawn('ls -la')
output = child.read()
print(output.decode('utf-8'))
child.close()

# 対話的プロセスのパターンマッチングと応答
child = pexpect.spawn('python3')
child.expect('>>>')
child.sendline('print("Hello, pexpect!")')
child.expect('>>>')
output = child.before.decode('utf-8')
print(f"出力: {output}")
child.sendline('exit()')
child.close()

上記の実行例は以下の様になります。

total 36
drwxr-xr-x  4 user2404 user2404 4096 Aug  7 23:25 .
drwxr-x--- 23 user2404 user2404 4096 Aug  7 23:29 ..
drwxr-xr-x  7 user2404 user2404 4096 Aug  7 23:25 .git
-rw-r--r--  1 user2404 user2404  109 Aug  7 23:25 .gitignore
-rw-r--r--  1 user2404 user2404    5 Aug  7 23:25 .python-version
drwxr-xr-x  4 user2404 user2404 4096 Aug  7 23:25 .venv
-rw-r--r--  1 user2404 user2404    0 Aug  7 23:25 README.md
-rw-r--r--  1 user2404 user2404  532 Aug  7 23:27 main.py
-rw-r--r--  1 user2404 user2404  181 Aug  7 23:25 pyproject.toml
-rw-r--r--  1 user2404 user2404 1703 Aug  7 23:25 uv.lock

出力:  print("Hello, pexpect!")
Hello, pexpect!

🐍pexpect関数と属性の説明

関数

関数名 説明
pexpect.spawn(command) 引数で設定したコマンドを子プロセスとして起動し、制御可能なプロセスオブジェクトを返す
pexpect.spawn(command, timeout=秒) timeoutオプション付きでプロセスを起動。指定秒数でタイムアウトする
child.read() プロセスオブジェクトからの出力を全て読み取ってバイト形式で返す
child.close() 子プロセスのオブジェクトを終了し、リソースを解放する
child.expect(pattern) 引数で指定したパターン(文字列や正規表現)が出力に現れるまで待機し、マッチした場合のインデックスを返す
child.expect([pattern1, pattern2, ...]) 複数のパターンをリストで指定し、最初にマッチしたもののインデックス(0から開始)を返す
child.sendline(text) 引数に指定したテキストに改行文字を付けて子プロセスのオブジェクトに送信する

属性

属性名 説明
child.before expect()でマッチした部分より前の出力内容をバイト形式で格納する

定数

定数名 説明
pexpect.TIMEOUT expect()でタイムアウトが発生した場合に返される値

例外

例外名 説明
pexpect.exceptions.EOF 子プロセスが予期せず終了した場合に発生する例外

🪶expectの基本的な使用方法

以下のように使用します。

実行方法

$ expect ./expect_test.exp
または
$ chmod +x ./expect_test.exp
$ ./expect_test.exp

プログラムの拡張子は.expとしますが、実際はTclのプログラムです。ただし、拡張子を.tclとしてもわかりにくいので.expとすることが多いと思います。また、プログラムの先頭のシバン(#!/usr/bin/expect -f)が重要なので間違えないようにしてください。

#!/usr/bin/expect -f

# 基本的なコマンド実行
spawn ls -la
expect eof

# パターンマッチングと応答
spawn python3
expect ">>>"
send "print('Hello, expect!')\r"
expect ">>>"
send "exit()\r"
expect eof

🪶expect関数と属性の説明

関数

関数名 説明
spawn command 指定したコマンドを子プロセスとして起動し、そのプロセスとの通信を開始する
expect pattern 指定したパターン(文字列や正規表現)が子プロセスの出力に現れるまで待機する
expect { } 複数の条件分岐を持つexpectブロック。複数のパターンに対して異なる処理を実行
send string 指定した文字列を子プロセスに送信する
set variable value 変数に値を設定する
puts string 指定した文字列を標準出力に表示する
exit code プログラムを終了する。終了コードを指定可能

特殊キーワード・記号

キーワード・記号 説明
eof End of File の略。プロセスが終了して出力が完了するまで待機する
timeout タイムアウト条件。指定時間内にパターンがマッチしなかった場合に実行される
\r キャリッジリターン(改行文字)。Enterキーを押すのと同等の効果
$variable 変数の値を参照する。setで設定した変数の内容を取得
{ } expectの複数条件分岐やコードのグループ化に使用

変数

変数名 説明
timeout expectの待機時間を秒単位で設定する特殊変数(デフォルト:10秒)

こちらのほうがshファイルに近い表記かなと思います。

pexpectexpectとの比較

記法・構文の比較

pexpectの場合のSSH自動ログイン実装の例

#!/usr/bin/env python3
import pexpect

hostname = "example.com"
username = "myuser"
password = "mypassword"

# SSH接続を開始
child = pexpect.spawn(f'ssh {username}@{hostname}', timeout=30)

try:
    index = child.expect(['password:', 'yes/no', pexpect.TIMEOUT])
    
    if index == 0:  # password:
        child.sendline(password)
        child.expect('$ ')
        child.sendline('ls -la')
        child.expect('$ ')
        child.sendline('exit')
    elif index == 1:  # yes/no
        child.sendline('yes')
        child.expect('password:')
        child.sendline(password)
        child.expect('$ ')
        child.sendline('ls -la')
        child.expect('$ ')
        child.sendline('exit')
    else:  # timeout
        print("接続がタイムアウトしました")
        
except pexpect.exceptions.EOF:
    pass

child.close()

expectの場合のSSH自動ログイン実装の例

#!/usr/bin/expect -f

set hostname "example.com"
set username "myuser"
set password "mypassword"
set timeout 30

# SSH接続を開始
spawn ssh $username@$hostname

# パターンマッチング
expect {
    "password:" {
        send "$password\r"
        expect "$ "
        send "ls -la\r"
        expect "$ "
        send "exit\r"
    }
    "yes/no" {
        send "yes\r"
        expect "password:"
        send "$password\r"
        expect "$ "
        send "ls -la\r"
        expect "$ "
        send "exit\r"
    }
    timeout {
        puts "接続がタイムアウトしました"
        exit 1
    }
}

expect eof

機能の比較

項目 pexpect(Python expect(Tcl)
ベース言語 Python Tcl
学習コスト Python知識を活用 Tcl習得が必要
機能性 豊富なPythonライブラリ 限定的
エラーの取り扱い 高度(try-catch等) 基本的
統合性 アプリケーション組み込み 単体ツール
起動速度 やや重い 高速
メモリ使用量 多い 少ない

使い分けの選択基準

現実的には、どちらも追加インストールが必要なので、用途によって選択するのが良いでしょう🤔 というと、元も子もないですけど。以下のような点を参考に選択するといいかなと思います。

pexpectを選ぶポイント

  • 複雑なロジックや統合が必要
  • エラーハンドリングが重要
  • 長期的な保守性が求められる
  • Pythonの豊富なライブラリを活用したい
  • アプリケーションへの組み込み

expectを選ぶポイント

  • シンプルで軽量な自動化が必要
  • システム管理の単発タスク
  • リソース制約がある環境
  • 既存のシェルスクリプトとの統合
  • 起動速度重視

よくある問題と解決策

1. 文字エンコーディングの問題

read()では読み込んだデータをバイト列として格納するので、画面出力時には明示的にエンコーディングを指定するのがよいでしょう。

child = pexpect.spawn('command')
child.encoding = 'utf-8'  # エンコーディングを明示的に設定
output = child.read()  # 自動的にUnicode文字列に変換

2. プロンプトの認識問題

# より具体的なパターンで待ち
child.expect(r'user@hostname:.*\$ ')  # 正規表現を使用

# 複数パターンの組み合わせで待ち
child.expect(['$ ', '#', '>>> '])  # 複数の可能性を考慮

3. タイムアウトの調整

# 長時間処理の待機
child.expect('pattern', timeout=300)  # 5分

# タイムアウトを無効化
child.expect('pattern', timeout=None)

おわりに

両方とも対話的プロセスの自動化することが可能になります。また、自動化することで手作業を削減し、人的エラーを防ぐことができます。まずは小さな自動化から始めて、徐々に適用範囲を拡大していくのがいいかなと思います🙂




以上の内容はhttps://uepon.hatenadiary.com/entry/2025/08/08/010114より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14