ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

subprocessを子プロセスまでkillして停止させる (python)

pythonのsubprocessライブラリ使用時、バックグラウンド実行したサブプロセスを中断させるのに手こずったのでメモします。

結論

下記の環境で確認済み。

バックグラウンド実行するため、subprocess.Popen()クラスを使用します。

Windows

シンプルにkill()メソッドを呼べば、シェルが実行した外部コマンドも止まります。ちなみにwindowsでは、kill()terminate()と同じ処理が実行されるので区別は必要ありません。

cmd = "some shell command"      # シェル実行のコマンド
p = subprocess.Popen(cmd)
p.kill()

Linux

kill()メソッドを呼ぶだけではシェルで呼び出した外部コマンドが動き続けるようです。以下の以下の方法で外部コマンドを止められます。

cmd = "some shell command"                          # シェル実行のコマンド
p = subprocess.Popen("exec " + cmd, shell=True)       # execで実行
p.kill()                                            # コマンドを停止

補足説明

execコマンド

Linuxのexecコマンドについては以下が参考になります。

qiita.com

通常シェルで外部コマンドを実行すると、

  1. 新しいプロセスIDが生成される
  2. 新しいプロセスIDで指定の外部コマンドを実行する

という手順を踏みます。上述のkill()メソッドはシェルのプロセスのみ停止するため、外部コマンドのプロセスは動いたままになります。execコマンドを使うと、現在のジョブと置き換えて外部コマンドが実行されるため、kill()メソッドで外部コマンドの実行を停止できるわけです。

このサブプロセスが生き残ってしまう現象はpythonのsubprocessでは困っている人が多いらしく、検索すると色々ヒットします。が、OSによって処理が異なったり、プロセスにKillシグナルを送る方法は複数あるため対応がまちまちになり、ややこしい印象です。個人的にはこの「execコマンド+Popen.kill()」が、Linuxコマンドとsubprocessライブラリで完結するので好きですね。

注意点としては、subprocess.Popen()shell=Trueで実行する必要があることです。コマンドを文字列で直接指定するため、ユーザーがコマンドを直接入力するようなケースではシェルインジェクションの危険性をケアする必要があります。まあそんな大層なものは作らないのですが・・・

subprocess

subprocessの使い方に関しては以下が参考になります。

dev.classmethod.jp

今回はバックグラウンド(非同期)処理で動かす際のtipsですが、同期処理として動かす方法もあります(subprocess.run())。時間のかからない処理であれば、run()を呼ぶほうが簡潔にかけそうです。標準入出力やパイプなんかも扱えるので使いこなすと色々便利そうです。

参考

https://docs.python.org/ja/3/library/subprocess.html#using-the-subprocess-module