#1. はじめに
今回はsubprocess.Popen()
とsubprocess.Popen().communicate()
の基本的な動作について記載する.
#2. 動作環境
- macOS Big Sur 11.4
- Python 3.7.9
#3. ソースコードと動作結果
##3.1. コマンドの実行
ファイルのコピーコマンドを実行する.
import subprocess
src = "/Users/ユーザ名/Desktop/a/test.txt"
dst = "/Users/ユーザ名/Desktop/b"
cmd = ["cp", src, dst]
subprocess.Popen(cmd)
または
import subprocess
src = "/Users/ユーザ名/Desktop/a/test.txt"
dst = "/Users/ユーザ名/Desktop/b"
cmd = "cp " + src + " " + dst
subprocess.Popen(cmd, shell=True)
後者の場合,ターミナルに入力するコマンド文字列がそのまま入るが,コマンドやオプション間に入れる半角スペースを忘れがちなので注意する.
また,shell=True
とすることでデメリットがある.
スペースやクォートなどの特殊文字(ターミナルでエスケープ文字が必要な文字)は,ターミナルで入力するときと同様にエスケープ処理する必要がある.
ちなみに,Windowsの場合は shell=True
にしていないとエラーになってしまうため,常にこれを設定する必要がある(前者の方法は使えない).
Windowsでの場合は shell=True
を設定してかつコマンドをリストまたは文字列で渡せば実行できるようだが,macの場合は shell=True
を設定してかつコマンドをリストで渡すとエラーが起こることもあることを確認した(cp
コマンド).
下記はそのときのエラー.
import subprocess
src = "/Users/ユーザ名/Desktop/a/test.txt"
dst = "/Users/ユーザ名/Desktop/b/"
cmd = ["cp", src, dst]
subprocess.Popen(cmd, shell=True)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file
cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
"""
##3.2. コマンドの出力の取得
フォルダの中身表示コマンドを実行する.
import subprocess
dir = "/Users/ユーザ名"
cmd = ["ls", dir]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
result = proc.communicate()
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
(b'Applications\nDesktop\nDocuments\nDownloads\nLibrary\nMovies\nMusic\nPictures\nPublic\n', None)
"""
stdout=subprocess.PIPE
を設定することで,communicate()
を通じて出力の取得が可能.
communicate()
の戻り値は2つ.
1番目に出力,2番目に None
.
1番目の出力がバイナリのため,デコードする.
2番目は本来はエラーが返ってくるが,エラーにパイプを通していないので None
が返ってくる.
import subprocess
dir = "/Users/ユーザ名"
cmd = ["ls", dir]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
result = proc.communicate()[0].decode("utf-8")
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
Applications
Desktop
Documents
Downloads
Library
Movies
Music
Pictures
Public
"""
ちなみに,stdout
を設定していない場合,なぜかコマンドの結果(正常終了の出力)がターミナルに表示されてしまう(環境にもよるかもしれないが,VSCodeでは表示された).
邪魔なので,stdout
は常に設定した方がいいかもしれない.
##3.3. コマンド結果がエラーだった場合の,エラーの取得
ls
のスペルをわざと lsa
と間違えてコマンドを実行する.
lsa
コマンドは無いので,command not found
といった内容の出力を期待値とする.
import subprocess
dir = "/Users/ユーザ名"
cmd = ["lsa", dir]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = proc.communicate()
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
Traceback (most recent call last):
File "/Users/ユーザ名/Desktop/test.py", line 7, in <module>
subprocess.Popen(cmd)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 800, in __init__
restore_signals, start_new_session)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 1551, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'cpa': 'cpa'
"""
出力の取得と同様,stderr=subprocess.PIPE
を設定することで,communicate()
を通じてエラーの出力が可能となるはず・・・.
コマンドのファイルが無いという FileNotFoundError
が発生した.
ファイルとして指定しているようなので,shell
オプションでコマンドとして指定する.
import subprocess
dir = "/Users/ユーザ名"
cmd = "lsa " + dir
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
result = proc.communicate()
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
(b'', b'/bin/sh: lsa: command not found\n')
"""
communicate()
の戻り値について,
1番目に b""
,2番目にエラー.
2番目の出力がバイナリのため,デコードする.
import subprocess
dir = "/Users/ユーザ名"
cmd = "lsa " + dir
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
result = proc.communicate()[1].decode("utf-8")
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
/bin/sh: lsa: command not found
"""
ちなみに,stderr=subprocess.PIPE
を設定した状態で,正常にコマンド実行すると次のようになる.
import subprocess
dir = "/Users/ユーザ名"
cmd = "ls " + dir
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
result = proc.communicate()
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 "/Users/ユーザ名/Desktop/test.py"
(b'Applications\nDesktop\nDocuments\nDownloads\nLibrary\nMovies\nMusic\nPictures\nPublic\n', b'')
"""
communicate()
の戻り値について,
1番目に 出力,2番目に b""
.
stderr
を指定したことで,None
ではなくバイナリ文字列が返ってくる.
ちなみに,stderr
を設定していない場合,なぜかコマンドの結果(エラー)がターミナルに表示されてしまう(環境にもよるかもしれないが,VSCodeでは表示された).
邪魔なので,stderr
は常に設定した方がいいかもしれない.
##3.4. コマンドのエラーを出力側から取得
3.3 と同様,ls
のスペルをわざと lsa
と間違えてコマンドを実行する.
lsa
コマンドは無いので,command not found
といった内容の出力を期待値とする.
ただし,out, err = communicate()
の out
にエラーを入れる.
import subprocess
dir = "/Users/ユーザ名"
cmd = "lsa " + dir
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
result = proc.communicate()
print(result)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 /Users/ユーザ名/Desktop/test.py
(b'/bin/sh: lsa: command not found\n', None)
"""
stderr=subprocess.STDOUT
を指定したことで,
1番目にエラー,2番目に None
が返ってくる.
##3.5. コマンドの結果が出力かエラーかを判断するフラグ
subprocess.Popen()
の戻り値からコマンドが正常に終了したかどうかを示すフラグがある.
それが returncode
.
import subprocess
cmd = "ls"
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
print(proc.returncode)
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 /Users/ユーザ名/Desktop/test.py
None
"""
subprocess --- サブプロセス管理 Popen.returncode
None はまだその子プロセスが終了していないことを示します。
終わるまで待てば良い?
subprocess.Popen().wait()
というメソッドがあった.これには timeout
のオプションもあった.
しかし,communicate()
にも timeout
のオプションがあった.
stdout
に PIPE
を通している(出力を取得することを想定している)ので,今回は communicate()
の timeout
で待つ.
(ちなみに,待つだけなら subprocess.Popen().wait()
使っても同じ.wait()
の戻り値は subprocess.Popen().returncode
と同じ.)
import subprocess
cmd = "ls"
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
try:
print(proc.returncode)
result = proc.communicate(timeout=5)
print(proc.returncode)
# タイムアウトが発生したら kill() によって終了する
except subprocess.TimeoutExpired:
proc.kill()
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 /Users/ユーザ名/Desktop/test.py
None
0
"""
subprocess --- サブプロセス管理 Popen.returncode
バージョン 3.8 で変更: Popen can use os.posix_spawn() in some cases for better performance. On Windows Subsystem for Linux and QEMU User Emulation, Popen constructor using os.posix_spawn() no longer raise an exception on errors like missing program, but the child process fails with a non-zero returncode.
Google翻訳
バージョン 3.8 で変更: Popen は、パフォーマンスを向上させるために、場合によっては os.posix_spawn() を使用できます。 Linux 用 Windows サブシステムおよび QEMU ユーザー エミュレーションでは、os.posix_spawn() を使用する Popen コンストラクターは、プログラムが見つからないなどのエラーで例外を発生させなくなりましたが、子プロセスはゼロ以外の戻りコードで失敗します。
「0以外の戻り値で失敗」とあるので、0で正常終了と考えて良い.
communicate(timeout=5)
直後の returncode
は 0
と表示されたので,正常終了したといえるだろう.
ちなみに,ls
を lsa
とした(存在しないコマンドを実行した)ときの returncode
は 127
だった.
また,存在するコマンドでエラーを発生させた場合は次のようになった.
import subprocess
src = "/Users/ユーザ名/Desktop/a/test.txt"
dst = "/Users/ユーザ名/Desktop/b/" # 存在しないフォルダを指定
# フォルダ名 b の直後にスラッシュを入れないと b というファイルとしてコピーされてしまう
cmd = "cp " + src + " " + dst
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
try:
print(proc.returncode)
result = proc.communicate(timeout=5)
print(proc.returncode)
# タイムアウトが発生したら kill() によって終了する
except subprocess.TimeoutExpired:
proc.kill()
""" 動作結果
コンピュータ名:~ ユーザ名$ /usr/local/bin/python3 /Users/ユーザ名/Desktop/test.py
None
cp: directory /Users/ユーザ名/Desktop/b does not exist
1
"""
#4. おわりに
深くは記載しない.