それでは毛玉諸君、これにて失敬

日々の精進を備忘録的に綴ります。

pythonのsubprocessについて調べたのでメモ

気付いたらまた体重が減っているko_ya346です。
意識して食事をとらねば…

この記事は?

pythonスクリプト上でコマンドを実行するときにsubprocessモジュールをよく使いますが、
実行の種類や引数が色々煩雑で使いこなせません。
公式ドキュメントを読んでも知識不足ゆえに分からないことが増えるだけで一向に理解が進まないので、自分で色々実験して理解することにしました。

情報量は少なめですが、同じ思いをしている人の手助けが出来ればと思います。

分からないこと

shell=Trueってなに?

例えばこんなコード

import subprocess
from subprocess import PIPE


CMD = "ls -a"
proc = subprocess.run(
    CMD, shell=True, stdout=PIPE, stderr=PIPE
)

これはls -apythonで実行してるだけなのですが、
引数のshell=Trueは一体なんなのでしょうか。

上のコードではコマンドを1つの文字として与えていますが、
shell=Trueを指定することで入力コマンドを認識してるのです!!ほえ~

ちなみに、shell=Falseの場合は

import subprocess
from subprocess import PIPE


CMD = ["ls", "-a"]
proc = subprocess.run(
    CMD, shell=False, stdout=PIPE, stderr=PIPE
)

のように、コマンドをlistで与えてあげると実行できます。
shell=Trueはコマンドをいい感じに認識してるんですね~

stdout=PIPEって何?

先ほどのサンプルコードにしれっと混ざっていた
こいつ→→→stdout=PIPE

これは、コマンド実行した結果の標準出力をどう扱うか指定する引数となります。
ここではPIPEに渡しているので、例えば

import subprocess
from subprocess import PIPE


CMD = "ls -a"
proc = subprocess.run(
    CMD, shell=True, stdout=PIPE, stderr=PIPE
)
print(proc.stdout)

のように呼び出すことが可能になるんです!!ほえ~

ただ、上のコマンドで得られる出力は

b'.\n..\nsubp_test.py\n\xe3\x81\x82\xe3\x82\x89\xe3\x82\x86\xe3\x82\x8b\xe3\x83\x91\xe3\x82\xb9
\xe3\x83\xaf\xe3\x83\xbc\xe3\x83\x89.txt\n\xe3\x83\x8f\xe3\x82\xa4\xe3\x83\x91\xe3\x83\xbcH\xe3\x81\xaa
\xe5\x8b\x95\xe7\x94\xbb.mp4\n\xe8\xaa\xb0\xe3\x81\xab\xe3\x82\x82\xe8\xa6\x8b\xe3\x81\x9b\xe3\x82\x89\
xe3\x82\x8c\xe3\x81\xaa\xe3\x81\x84\xe6\x81\xa5\xe3\x81\x9a\xe3\x81\x8b\xe3\x81\x97\xe3\x81\x84\xe5\x86
\x99\xe7\x9c\x9f.png\n'

と人間には読めないバイナリ型として取得されます。
そのため読みやすくするためには以下のようにデコードが必要です。

import subprocess
from subprocess import PIPE


CMD = "ls -a"
proc = subprocess.run(
    CMD, shell=True, stdout=PIPE, stderr=PIPE
)

for line in proc.stdout.decode("utf8").split("\n"):
    print(line)
# 実行結果

.
..
subp_test.py
あらゆるパスワード.txt
ハイパーHな動画.mp4
誰にも見せられない恥ずかしい写真.png

うまく読めるようになりました。何やら恐ろしいファイルが置いてある場所でしたね。読めない方が良かったかも

ちなみに、ターミナル上に表示するだけなら
PIPEの代わりにsys.stdoutを渡すことでも出来ます。

import sys
import subprocess


CMD = "ls -a"
subprocess.run(
    CMD, shell=True, stdout=sys.stdout, stderr=sys.stdout
)
# 実行結果
.  ..  subp_test.py  あらゆるパスワード.txt  ハイパーHな動画.mp4  誰にも見せられない恥ずかしい写真.png

良い感じです。

subprocess.runとsubprocess.Popenの違いは何?

subprocess.runは同期処理、
subprocess.Popenは非同期処理という違いがあります。

subprocess.Popenに処理を渡すと、pythonプロセスとは別で処理を行ってくれます。

例えば次のような処理

import sys
import subprocess

CMD = "sleep 5; echo Finish Popen proc

subprocess.Popen(
    CMD, shell=True, stdout=sys.stdout
)

print("Im python process!!! hahaha ")

subprocess.Popenには5秒後に出力を行う指令を出し、
その直後にpythonのprintを実行させてみます。

まず、実行直後はこんな感じ
f:id:ko_ya346:20210718223623p:plain

subprocess.Popenに処理を渡し、その後のprint処理を先に実行しています。

その後、しばらくすると

f:id:ko_ya346:20210718223625p:plain お?
subprocess.Popenの処理が終わり、出力を受け取りました。
別プロセスで処理が行える場合は、こちらを使った方が効率がよいですね!

参考記事

subprocess で shell=True でリストを与えたときの挙動 - Qiita

Pythonからシェルコマンドを実行!subprocessでサブプロセスを実行する方法まとめ | DevelopersIO