この記事を作った動機

 Onenote代替品の作成の研究の一環で、pexpect を使って端末のセッションを維持させながらとにかくコマンドを実行させたいということがあり、その過程で試行錯誤があったため記録したい。

現状使っているもの

 以下は現状使っている実装の参考例である。一定時間置いたあとに、何でもマッチングするexpectを配置しているのは、標準出力であるstdoutに対して、コマンドの実行結果を吐き出させたいためである。作ったセッション内のターミナルに何が出力されるかわからないが、とにかくコマンドを実行させて、その結果を出力することに以下のスクリプトは特化している。

 待ち時間は適宜調整する必要がある。

スクリプト本体

import pexpect,time,sys

session = pexpect.spawn("/bin/bash",timeout=5, encoding='utf-8')
session.logfile_read = sys.stdout

session.sendline("ls")
time.sleep(1)
session.expect(".+")

session.sendline("gcc")
time.sleep(1)
session.expect(".+")

実行結果

python main1.py
ls
# ⠀aaaa  ⠀main.py  ⠀main1.py  ⠀readme.md  ⠀test/
gcc
# gcc: fatal error: 入力ファイルがありません
# compilation terminated.

追記 (2026/9/16)

 このやり方は非常に簡易的にしか動作せず、副作用があることが分かった。具体的には".+“でexpectさせるとログが出てくるのは、pexpectが期待した文字列を待機している状態になるからであることが分かった。つまるところ、タイムアウトに引っかかると例外を出して強制終了してしまう。

 それを防ぐには、pexpectにおいては、waitという専用の関数があるため、それを利用することができることが分かった。ただこれだとログが出てこない(”.*“と同じ挙動になる)ため、なにかさらに工夫が必要そうなことが分かっている。

 また、pexpect にて同じ端末に出力を出させると端末の表示がぐちゃぐちゃになってしまう。最近考えていることとして個別にファイルとしてlogをテキストファイルとして書き出させてそれをwatch -n 0.1 "cat log | tail -n 10"みたいにすればよいのではないかと思い始めている。

うまくいかなかったこと

単にsendlineする

 単にsendlineすると、コマンド自体は実行されるが、一切標準出力にログが出力されないことになる。

スクリプト本体

import pexpect
import time,sys

session = pexpect.spawn("/bin/bash",timeout=5, encoding='utf-8')
session.logfile_read = sys.stdout

session.sendline("ls")
time.sleep(1)

session.sendline("echo test > test1")
time.sleep(1)

実行結果

python main2.py 
# 何も出てこない
cat test1
# test

エンコードを指定しない

 端末のセッションを作るときに、文字コードを明示しなかった場合、そもそもスクリプトを実行できないことがある。

スクリプト本体

import pexpect
import time,sys

session = pexpect.spawn("/bin/bash")
session.logfile = sys.stdout

session.sendline("ls")
time.sleep(1)

session.sendline("echo test > test1")
time.sleep(1)

実行結果

python main3.py 
# Traceback (most recent call last):
#   File "/home/UserName/work/free/2026-09-08-23-14-06-1788876846/main3.py", line 7, in <module>
#     session.sendline("ls")
#     ~~~~~~~~~~~~~~~~^^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/site-packages/pexpect/pty_spawn.py", line 578, in sendline
#     return self.send(s + self.linesep)
#            ~~~~~~~~~^^^^^^^^^^^^^^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/site-packages/pexpect/pty_spawn.py", line 566, in send
#     self._log(s, 'send')
#     ~~~~~~~~~^^^^^^^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/site-packages/pexpect/spawnbase.py", line 129, in _log
#     self.logfile.write(s)
#     ~~~~~~~~~~~~~~~~~~^^^
# TypeError: write() argument must be str, not bytes

*を expect させる

 不正な正規表現になり、*が繰り返すべき文字列が指定されていないというエラーになる。

スクリプト本体

import pexpect
import time,sys

session = pexpect.spawn("/bin/bash",timeout=5, encoding='utf-8')
session.logfile_read = sys.stdout

session.sendline("ls")
time.sleep(1)
session.expect("*")

session.sendline("gcc")
time.sleep(1)
session.expect("*")

実行結果

python main4.py 
# Traceback (most recent call last):
#   File "/home/UserName/work/free/2026-09-08-23-14-06-1788876846/main4.py", line 9, in <module>
#     session.expect("*")
#     ~~~~~~~~~~~~~~^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/site-packages/pexpect/spawnbase.py", line 353, in expect
#     compiled_pattern_list = self.compile_pattern_list(pattern)
#   File "/home/UserName/.conda/envs/test/lib/python3.14/site-packages/pexpect/spawnbase.py", line 242, in compile_pattern_list
#     compiled_pattern_list.append(re.compile(p, compile_flags))
#                                  ~~~~~~~~~~^^^^^^^^^^^^^^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/__init__.py", line 289, in compile
#     return _compile(pattern, flags)
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/__init__.py", line 350, in _compile
#     p = _compiler.compile(pattern, flags)
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/_compiler.py", line 762, in compile
#     p = _parser.parse(p, flags)
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/_parser.py", line 973, in parse
#     p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/_parser.py", line 460, in _parse_sub
#     itemsappend(_parse(source, state, verbose, nested + 1,
#                 ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#                        not nested and not items))
#                        ^^^^^^^^^^^^^^^^^^^^^^^^^
#   File "/home/UserName/.conda/envs/test/lib/python3.14/re/_parser.py", line 687, in _parse
#     raise source.error("nothing to repeat",
#                        source.tell() - here + len(this))
# re.PatternError: nothing to repeat at position 0

.*を expect させる

 様々な文字列を表す、.*によって無限回の繰り返しとして、全てにマッチングするようにするとエラーは出てこないが、標準出力に実行結果が出てこない結末となる。

スクリプト本体

import pexpect
import time,sys

session = pexpect.spawn("/bin/bash",timeout=5, encoding='utf-8')
session.logfile_read = sys.stdout

session.sendline("ls")
time.sleep(1)
session.expect(".*")

session.sendline("gcc")
time.sleep(1)
session.expect(".*")

実行結果

python main5.py 
# 何も出力されない

使った画像とか

参考にしたサイトとか