読み込み中...

Pythonのsubprocess.Popen()メソッドを使った基本的な使い方10選

subprocess.Popen 徹底解説 Python
この記事は約25分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●subprocess.Popenとは?

Pythonでシステムコマンドを実行する方法を探していませんか?

subprocess.Popenは、そんなあなたの強い味方となるでしょう。

多くの開発者が日々の作業で直面する課題、つまりPythonスクリプトから外部コマンドを柔軟に制御する必要性に対して、subprocess.Popenは優れた解決策を提供します。

subprocess.Popenは、Pythonの標準ライブラリsubprocessモジュールの中核をなすクラスです。

新しいプロセスを生成し、そのプロセスとの通信を可能にします。

単純なコマンド実行から複雑なパイプライン処理まで、幅広いシナリオに対応できる柔軟性が魅力です。

○Popenの特徴と他のsubprocess関数との違い

subprocess.Popenの特徴を理解するには、同じsubprocessモジュールの他の関数と比較するのが効果的です。

例えば、subprocess.runやsubprocess.callといった関数も外部コマンドの実行に使用されますが、Popenには独自の利点があります。

subprocess.runは Python 3.5 以降で導入された比較的新しい関数です。

シンプルなコマンド実行には適していますが、プロセスの詳細な制御には向いていません。

一方、Popenはプロセスの生成から終了まで、より細かな制御が可能です。

実際に、Popenを使用すると、標準入出力やエラー出力のリアルタイムな取得、プロセスの状態監視、タイムアウト設定など、高度な操作が可能になります。

また、非同期処理との親和性も高く、複数のプロセスを並行して管理するシナリオにも対応できます。

○Popenオブジェクトの主要メソッド解説

Popenオブジェクトには、プロセス制御に関する様々なメソッドが用意されています。

それぞれのメソッドを理解することで、より効果的にPopenを活用できるようになります。

まず、communicate()メソッドは非常に重要です。標準入力にデータを送信し、標準出力とエラー出力を取得するのに使用します。

例えば、次のようなコードで外部コマンドの出力を取得できます。

import subprocess

process = subprocess.Popen(['echo', 'Hello, World!'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()

print(stdout.decode())  # 出力: Hello, World!

このコードを実行すると、「Hello, World!」が標準出力に表示されます。

wait()メソッドもよく使用されます。プロセスの終了を待機し、終了コードを取得します。

長時間実行されるコマンドを扱う際に特に有用です。

import subprocess
import time

process = subprocess.Popen(['sleep', '5'])
print("プロセス開始")
return_code = process.wait()
print(f"プロセス終了 (終了コード: {return_code})")

この例では、5秒間スリープするコマンドを実行し、その終了を待機します。

実行すると、次のような出力が得られます。

プロセス開始
(5秒後)
プロセス終了 (終了コード: 0)

poll()メソッドは、プロセスの状態を非ブロッキングで確認するのに使用します。

プロセスが終了していれば終了コードを、まだ実行中であればNoneを返します。

import subprocess
import time

process = subprocess.Popen(['sleep', '3'])
while True:
    return_code = process.poll()
    if return_code is not None:
        print(f"プロセス終了 (終了コード: {return_code})")
        break
    print("プロセス実行中...")
    time.sleep(1)

この例を実行すると、次のような出力が得られます。

プロセス実行中...
プロセス実行中...
プロセス実行中...
プロセス終了 (終了コード: 0)

私たちはPythonのsubprocess.Popenについて、その基本的な概念と主要なメソッドを見てきました。

実際の開発現場では、様々なシナリオに応じてPopenの特性を活用することになります。

●subprocess.Popenの基本的な使い方10選

Pythonのsubprocess.Popenは、システムコマンドを実行するための強力な機能です。

日々のプログラミング作業で、外部コマンドの実行や制御が必要になることはよくあります。

そんな時、subprocess.Popenの基本的な使い方を知っていると、作業効率が大幅に向上します。

では、実際にsubprocess.Popenの基本的な使い方を10個のサンプルコードを通じて学んでいきましょう。

各サンプルコードは、実際の開発シーンで遭遇しそうな状況を想定しています。

○サンプルコード1:シンプルなコマンド実行

まずは、最も基本的な使用方法から始めましょう。

単純なコマンドを実行する方法です。

import subprocess

# 'echo' コマンドを実行
process = subprocess.Popen(['echo', 'Hello, World!'])
process.wait()

このコードは、’echo’ コマンドを使って “Hello, World!” を出力します。

subprocess.Popen() 関数は、コマンドとその引数をリストとして受け取ります。

process.wait() は、プロセスが終了するまで待機します。

実行結果

Hello, World!

○サンプルコード2:標準出力の取得

コマンドの出力を取得したい場合もあるでしょう。

標準出力を取得する方法を見てみましょう。

import subprocess

# 'echo' コマンドを実行し、標準出力を取得
process = subprocess.Popen(['echo', 'Hello, Python!'], stdout=subprocess.PIPE, text=True)
output, error = process.communicate()

print(f"コマンドの出力: {output}")

ここでは、stdout=subprocess.PIPE を指定して標準出力をキャプチャし、text=True で出力をテキストとして扱います。

process.communicate() は、プロセスの終了を待ち、標準出力とエラー出力を返します。

実行結果

コマンドの出力: Hello, Python!

○サンプルコード3:標準エラー出力の取得

エラーが発生した場合、その内容を取得したいことがあります。

標準エラー出力を取得する方法を見てみましょう。

import subprocess

# 存在しないコマンドを実行し、標準エラー出力を取得
process = subprocess.Popen(['non_existent_command'], stderr=subprocess.PIPE, text=True)
output, error = process.communicate()

print(f"エラー出力: {error}")

stderr=subprocess.PIPE を指定することで、標準エラー出力をキャプチャします。

存在しないコマンドを実行しているため、エラーメッセージが出力されます。

実行結果(環境によって異なる場合があります)

エラー出力: /bin/sh: non_existent_command: command not found

○サンプルコード4:入力の送信

対話式のコマンドに入力を送信したい場合もあるでしょう。

入力を送信する方法を見てみましょう。

import subprocess

# 'cat' コマンドを実行し、標準入力から読み取る
process = subprocess.Popen(['cat'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
output, error = process.communicate(input="Hello from Python!\n")

print(f"コマンドの出力: {output}")

stdin=subprocess.PIPE を指定することで、標準入力にデータを送信できるようになります。

process.communicate() の input パラメータを使って、データを送信します。

実行結果

コマンドの出力: Hello from Python!

○サンプルコード5:タイムアウトの設定

長時間実行されるコマンドに対して、タイムアウトを設定したい場合があります。

タイムアウトを設定する方法を見てみましょう。

import subprocess
import time

# 'sleep' コマンドを実行し、タイムアウトを設定
process = subprocess.Popen(['sleep', '10'])

try:
    process.wait(timeout=5)
    print("プロセスが正常に終了しました")
except subprocess.TimeoutExpired:
    print("タイムアウトが発生しました")
    process.kill()
    process.wait()

process.wait() にタイムアウト時間を指定することで、指定した時間を超えた場合に subprocess.TimeoutExpired 例外が発生します。

例外をキャッチしてプロセスを強制終了させることができます。

実行結果

タイムアウトが発生しました

subprocess.Popenを使用すると、外部コマンドの実行をより柔軟に制御できます。

シンプルなコマンド実行から、出力の取得、入力の送信、タイムアウトの設定まで、様々なシナリオに対応できることがわかりました。

○サンプルコード6:環境変数の設定

プログラムの実行環境を制御するために、環境変数を設定することが必要な場面があります。

subprocess.Popenを使用して、特定の環境変数を設定しながらコマンドを実行する方法を見てみましょう。

import subprocess
import os

# 現在の環境変数をコピー
env = os.environ.copy()

# 新しい環境変数を追加
env['MY_CUSTOM_VAR'] = 'Hello from Python!'

# 環境変数を表示するコマンドを実行
process = subprocess.Popen(['env'], env=env, stdout=subprocess.PIPE, text=True)
output, _ = process.communicate()

print("環境変数の出力:")
print(output)

このコードでは、まず os.environ.copy() を使用して現在の環境変数のコピーを作成します。

新しい環境変数 ‘MY_CUSTOM_VAR’ を追加し、env パラメータを使用して subprocess.Popen に渡します。

‘env’ コマンドを実行して、設定された環境変数を表示します。

実行結果(環境によって出力が異なる場合があります)

環境変数の出力:
MY_CUSTOM_VAR=Hello from Python!
PATH=/usr/local/bin:/usr/bin:/bin
...(他の環境変数)

○サンプルコード7:シェルコマンドの実行

時には、複雑なシェルコマンドを実行したい場合があります。

subprocess.Popenを使用してシェルコマンドを実行する方法を見てみましょう。

import subprocess

# シェルコマンドを実行
shell_command = "echo 'Current date:' $(date)"
process = subprocess.Popen(shell_command, shell=True, stdout=subprocess.PIPE, text=True)
output, _ = process.communicate()

print("シェルコマンドの出力:")
print(output)

このコードでは、shell=True パラメータを使用してシェルを介してコマンドを実行します。

複雑なコマンド(この場合、echo と date の組み合わせ)を文字列として渡すことができます。

実行結果(実行時の日付によって異なります)

シェルコマンドの出力:
Current date: Thu Jul  4 12:34:56 JST 2024

○サンプルコード8:バックグラウンド実行

長時間実行されるプロセスをバックグラウンドで実行し、その間に他の処理を行いたい場合があります。

subprocess.Popenを使用してバックグラウンド実行を行う方法を見てみましょう。

import subprocess
import time

# バックグラウンドでプロセスを開始
process = subprocess.Popen(['sleep', '5'])

print("プロセスがバックグラウンドで実行中...")

# 他の処理を実行
for i in range(5):
    print(f"別の処理を実行中... ({i+1}秒経過)")
    time.sleep(1)

# プロセスの終了を待機
process.wait()
print("バックグラウンドプロセスが終了しました")

このコードでは、’sleep’ コマンドを使用して5秒間待機するプロセスをバックグラウンドで開始します。

その間、別のループ処理を実行し、最後にプロセスの終了を待ちます。

実行結果

プロセスがバックグラウンドで実行中...
別の処理を実行中... (1秒経過)
別の処理を実行中... (2秒経過)
別の処理を実行中... (3秒経過)
別の処理を実行中... (4秒経過)
別の処理を実行中... (5秒経過)
バックグラウンドプロセスが終了しました

○サンプルコード9:複数コマンドのパイプライン

Unix系システムでよく使用されるパイプライン(|)を使って複数のコマンドを連携させる方法を見てみましょう。

subprocess.Popenを使用してパイプラインを実現する方法を紹介します。

import subprocess

# パイプラインを使用して複数のコマンドを実行
cmd1 = subprocess.Popen(['echo', 'Hello, World!'], stdout=subprocess.PIPE)
cmd2 = subprocess.Popen(['wc', '-w'], stdin=cmd1.stdout, stdout=subprocess.PIPE)

output, _ = cmd2.communicate()

print(f"単語数: {output.decode().strip()}")

このコードでは、’echo’ コマンドの出力を ‘wc -w’ コマンド(単語数をカウント)の入力にパイプで渡しています。

cmd1.stdout を cmd2 の stdin として使用することで、パイプラインを実現しています。

実行結果

単語数: 2

subprocess.Popenを使用することで、環境変数の設定、シェルコマンドの実行、バックグラウンド処理、そしてコマンドのパイプライン処理など、より高度なシステム操作が可能になります。

●subprocess.Popenのエラー処理と対策

subprocess.Popenを使用してシステムコマンドを実行する際、様々なエラーに遭遇する可能性があります。

エラーを適切に処理し、対策を講じることは、堅牢なプログラムを作成する上で非常に重要です。

ここでは、よく遭遇するエラーとその解決法、そしてセキュリティ上の注意点について詳しく見ていきましょう。

○よくあるエラーとその解決法

subprocess.Popenを使用する際、いくつかの一般的なエラーが発生する可能性があります。

それぞれのエラーについて、具体的な例を挙げながら解決法を説明します。

□FileNotFoundError

最もよく遭遇するエラーの一つが、FileNotFoundErrorです。

実行しようとしたコマンドが見つからない場合に発生します。

import subprocess

try:
    process = subprocess.Popen(['non_existent_command'])
    process.communicate()
except FileNotFoundError as e:
    print(f"エラー: コマンドが見つかりません - {e}")

実行結果

エラー: コマンドが見つかりません - [Errno 2] No such file or directory: 'non_existent_command'

解決法としては、コマンドのパスが正しいか確認し、必要に応じて完全なパスを指定することが挙げられます。

また、環境変数PATHにコマンドのディレクトリが含まれているか確認することも重要です。

□PermissionError

コマンドを実行する権限がない場合、PermissionErrorが発生します。

import subprocess

try:
    process = subprocess.Popen(['/root/some_restricted_command'])
    process.communicate()
except PermissionError as e:
    print(f"エラー: 権限がありません - {e}")

実行結果

エラー: 権限がありません - [Errno 13] Permission denied: '/root/some_restricted_command'

解決策としては、適切な権限を持つユーザーとしてスクリプトを実行するか、必要最小限の権限を付与することが考えられます。

□TimeoutExpired

プロセスが指定された時間内に終了しない場合、TimeoutExpiredエラーが発生します。

import subprocess

try:
    process = subprocess.Popen(['sleep', '10'])
    process.communicate(timeout=5)
except subprocess.TimeoutExpired as e:
    print(f"エラー: タイムアウトしました - {e}")
    process.kill()

実行結果

エラー: タイムアウトしました - Command '['sleep', '10']' timed out after 5 seconds

タイムアウトが発生した場合、プロセスを強制終了するなどの対策を講じる必要があります。

上記の例では、process.kill()を使用してプロセスを終了しています。

□CalledProcessError

プロセスが非ゼロの終了コードで終了した場合、CalledProcessErrorが発生します。

import subprocess

try:
    subprocess.run(['ls', '/non_existent_directory'], check=True)
except subprocess.CalledProcessError as e:
    print(f"エラー: プロセスが失敗しました - 終了コード: {e.returncode}, 出力: {e.output}")

実行結果

エラー: プロセスが失敗しました - 終了コード: 2, 出力: None

解決策としては、エラーコードに基づいて適切な対応を行うことが重要です。

例えば、特定のエラーコードに対して再試行を行うなどの処理を実装できます。

○セキュリティ上の注意点

subprocess.Popenを使用する際は、セキュリティにも十分な注意を払う必要があります。

特に、ユーザー入力を直接コマンドとして実行することは危険です。

□シェルインジェクション対策

ユーザー入力を含むコマンドを実行する際は、shell=Trueオプションの使用を避け、引数をリストとして渡すことをお勧めします。

import subprocess

user_input = input("ファイル名を入力してください: ")

# 危険な方法(推奨されません)
# subprocess.Popen(f"cat {user_input}", shell=True)

# 安全な方法
subprocess.Popen(['cat', user_input])

□入力のサニタイズ

ユーザー入力を使用する前に、適切にサニタイズすることが重要です。

例えば、許可されたファイル名のパターンのみを受け入れるなどの対策が考えられます。

import re
import subprocess

def is_safe_filename(filename):
    return re.match(r'^[a-zA-Z0-9_.-]+$', filename) is not None

user_input = input("ファイル名を入力してください: ")

if is_safe_filename(user_input):
    subprocess.Popen(['cat', user_input])
else:
    print("無効なファイル名です")

□最小権限の原則

プロセスを実行する際は、必要最小限の権限で実行することが重要です。

例えば、root権限が不要な操作では、一般ユーザー権限で実行するべきです。

□タイムアウトの設定

長時間実行されるプロセスには適切なタイムアウトを設定し、リソースの過剰な消費を防ぐことが重要です。

import subprocess

try:
    process = subprocess.Popen(['some_long_running_command'])
    process.communicate(timeout=60)  # 60秒のタイムアウト
except subprocess.TimeoutExpired:
    process.kill()
    print("プロセスがタイムアウトしました")

subprocess.Popenを使用する際は、エラー処理とセキュリティ対策を適切に実装することが非常に重要です。

エラーを予期し、適切に対処することで、より堅牢なプログラムを作成できます。

また、セキュリティ上の注意点を守ることで、安全なアプリケーションの開発が可能になります。

この知識を身につけることで、システムレベルの操作を含むより高度なPythonアプリケーションを開発する能力が向上し、チーム内でのsubprocess関連の技術的な相談にも対応できるようになるでしょう。

●subprocess.Popenの高度な使用法

subprocess.Popenの基本的な使い方を習得したら、次はより高度な使用法にチャレンジしてみましょう。

高度な使用法を学ぶことで、より複雑なシステム操作やプロセス管理が可能になり、効率的で柔軟なプログラムを作成できるようになります。

○非同期処理との組み合わせ

非同期処理とsubprocess.Popenを組み合わせることで、複数のプロセスを並行して実行し、効率的にタスクを処理できます。

Pythonの非同期ライブラリであるasyncioとsubprocess.Popenを組み合わせた例を見てみましょう。

import asyncio
import subprocess

async def run_command(cmd):
    process = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await process.communicate()
    return stdout, stderr

async def main():
    commands = [
        ['echo', 'Hello'],
        ['echo', 'World'],
        ['sleep', '2'],
        ['echo', 'Goodbye']
    ]
    tasks = [run_command(cmd) for cmd in commands]
    results = await asyncio.gather(*tasks)
    for i, (stdout, stderr) in enumerate(results):
        print(f"Command {i + 1} output: {stdout.decode().strip()}")

asyncio.run(main())

このコードでは、asyncioを使用して複数のコマンドを非同期に実行しています。

run_command関数は各コマンドを非同期に実行し、main関数でそれらを並行して実行しています。

実行結果

Command 1 output: Hello
Command 2 output: World
Command 3 output: 
Command 4 output: Goodbye

非同期処理を使用することで、’sleep’ コマンドの実行中も他のコマンドが並行して実行され、全体的な実行時間が短縮されます。

○サブプロセスの監視と制御

長時間実行されるプロセスや、リソースを多く消費するプロセスを扱う場合、サブプロセスの監視と制御が重要になります。

プロセスの状態を定期的にチェックし、必要に応じて終了させる例を見てみましょう。

import subprocess
import time
import psutil

def monitor_process(process, max_cpu_percent=50, check_interval=1):
    start_time = time.time()
    while process.poll() is None:
        try:
            p = psutil.Process(process.pid)
            cpu_percent = p.cpu_percent(interval=check_interval)
            print(f"CPU使用率: {cpu_percent}%")
            if cpu_percent > max_cpu_percent:
                print(f"CPU使用率が{max_cpu_percent}%を超えました。プロセスを終了します。")
                process.terminate()
                break
        except psutil.NoSuchProcess:
            print("プロセスが終了しました。")
            break
    end_time = time.time()
    print(f"実行時間: {end_time - start_time:.2f}秒")

# CPUを使用するダミープロセス
cpu_intensive_command = "python -c \"import time; [x**2 for x in range(10**7)]\""
process = subprocess.Popen(cpu_intensive_command, shell=True)

monitor_process(process)

このコードでは、psutilライブラリを使用してプロセスのCPU使用率を監視しています。

CPU使用率が指定した閾値を超えた場合、プロセスを終了させます。

実行結果(環境によって異なる場合があります)

CPU使用率: 25.0%
CPU使用率: 75.0%
CPU使用率が50%を超えました。プロセスを終了します。
実行時間: 2.03秒

サブプロセスの監視と制御を実装することで、リソースの過剰な使用を防ぎ、システムの安定性を確保できます。

○リソース管理の最適化

subprocess.Popenを使用する際、メモリやファイルディスクリプタなどのリソース管理も重要です。

特に大量のプロセスを扱う場合、リソースの最適化が必要になります。

ここでは、ファイルディスクリプタの制限に対処する例を見てみましょう。

import subprocess
import resource
import os

def set_file_descriptor_limit(limit):
    soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
    resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))

def run_multiple_processes(num_processes):
    processes = []
    for i in range(num_processes):
        process = subprocess.Popen(['echo', f'Process {i}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        processes.append(process)

    for process in processes:
        stdout, stderr = process.communicate()
        print(stdout.decode().strip())

# ファイルディスクリプタの制限を設定
set_file_descriptor_limit(1024)

# プロセス数
num_processes = 500

try:
    run_multiple_processes(num_processes)
except OSError as e:
    print(f"エラーが発生しました: {e}")
    print("ファイルディスクリプタの制限に達した可能性があります。")

このコードでは、resource モジュールを使用してファイルディスクリプタの制限を設定しています。

大量のプロセスを実行する際に、システムのリソース制限に達する可能性があるため、適切に制限を設定することが重要です。

実行結果(システムの設定によって異なる場合があります)

Process 0
Process 1
...
Process 499

リソース管理を最適化することで、より多くのプロセスを安定して実行できるようになります。

システムの制限に注意を払い、適切な設定を行うことが重要です。

まとめ

Pythonのsubprocess.Popenは、システムコマンドを実行し、外部プロセスを制御するための強力な機能です。

本記事では、subprocess.Popenの基本から高度な使用法まで、幅広くカバーしてきました。

今回学んだ知識を基に、さらなる探求と実践を重ね、Pythonエンジニアとしてのスキルを磨いていってください。

皆さんのPython開発が、subprocess.Popenを活用することでより豊かで効率的なものになることを願っています。