PHPで外部プロセスを実行する際、そのプロセスが現在実行中なのか、終了したのか、どのような状態なのかを知りたい場合があります。この記事では、PHPのproc_get_status関数について、基本的な使い方から実践的な監視方法まで詳しく解説していきます。
proc_get_status関数とは?
proc_get_statusは、proc_openで開始したプロセスの現在の状態を取得する関数です。プロセスがまだ実行中か、終了したか、終了コードは何かなど、詳細な情報を取得できます。
基本的な構文
array|false proc_get_status(resource $process)
パラメータ:
$process:proc_openで開いたプロセスリソース
戻り値:
- プロセスの状態情報を含む連想配列
- 失敗した場合は
false
戻り値の配列要素
| キー | 型 | 説明 |
|---|---|---|
command | string | 実行されたコマンド |
pid | int | プロセスID |
running | bool | プロセスが実行中かどうか |
signaled | bool | シグナルで終了したかどうか |
stopped | bool | プロセスが停止しているか |
exitcode | int | 終了コード(実行中の場合は-1) |
termsig | int | 終了シグナル番号 |
stopsig | int | 停止シグナル番号 |
基本的な使い方
シンプルなステータス確認
<?php
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
// 長時間実行されるコマンドを開始
$process = proc_open('sleep 10', $descriptorspec, $pipes);
if (is_resource($process)) {
// ステータスを取得
$status = proc_get_status($process);
echo "コマンド: {$status['command']}\n";
echo "プロセスID: {$status['pid']}\n";
echo "実行中: " . ($status['running'] ? 'はい' : 'いいえ') . "\n";
// パイプを閉じる
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
// プロセスを閉じる
proc_close($process);
}
?>
プロセスの完了待機
<?php
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
$process = proc_open('long-running-command', $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
// プロセスが終了するまで待機
do {
$status = proc_get_status($process);
if ($status['running']) {
echo "実行中... (PID: {$status['pid']})\n";
sleep(1);
}
} while ($status['running']);
echo "プロセスが終了しました\n";
echo "終了コード: {$status['exitcode']}\n";
// 出力を取得
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
}
?>
実践的な使用例
1. プロセスモニタークラス
<?php
/**
* プロセス監視クラス
*/
class ProcessMonitor {
private $process;
private $pipes;
private $startTime;
/**
* プロセスを開始
*/
public function start($command) {
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
$this->process = proc_open($command, $descriptorspec, $this->pipes);
if (!is_resource($this->process)) {
throw new Exception('プロセスの起動に失敗しました');
}
// 標準入力は使用しないので閉じる
fclose($this->pipes[0]);
// ノンブロッキングモードに設定
stream_set_blocking($this->pipes[1], false);
stream_set_blocking($this->pipes[2], false);
$this->startTime = microtime(true);
return $this->getStatus();
}
/**
* 現在のステータスを取得
*/
public function getStatus() {
if (!is_resource($this->process)) {
return null;
}
$status = proc_get_status($this->process);
// 実行時間を追加
if ($status) {
$status['elapsed_time'] = microtime(true) - $this->startTime;
}
return $status;
}
/**
* プロセスが実行中かチェック
*/
public function isRunning() {
$status = $this->getStatus();
return $status && $status['running'];
}
/**
* プロセスIDを取得
*/
public function getPid() {
$status = $this->getStatus();
return $status ? $status['pid'] : null;
}
/**
* 標準出力を読み取る(ノンブロッキング)
*/
public function readOutput() {
if (!isset($this->pipes[1])) {
return '';
}
return stream_get_contents($this->pipes[1]);
}
/**
* 標準エラー出力を読み取る(ノンブロッキング)
*/
public function readError() {
if (!isset($this->pipes[2])) {
return '';
}
return stream_get_contents($this->pipes[2]);
}
/**
* プロセスを強制終了
*/
public function terminate($signal = 15) {
if (is_resource($this->process)) {
proc_terminate($this->process, $signal);
}
}
/**
* タイムアウト付きで完了を待つ
*/
public function waitForCompletion($timeout = null) {
$output = '';
$errors = '';
while ($this->isRunning()) {
$status = $this->getStatus();
// タイムアウトチェック
if ($timeout !== null && $status['elapsed_time'] > $timeout) {
$this->terminate();
throw new Exception("プロセスがタイムアウトしました({$timeout}秒)");
}
// 出力を読み取る
$output .= $this->readOutput();
$errors .= $this->readError();
usleep(100000); // 0.1秒待機
}
// 残りの出力を読み取る
$output .= $this->readOutput();
$errors .= $this->readError();
$status = $this->getStatus();
return [
'exit_code' => $status['exitcode'],
'output' => $output,
'errors' => $errors,
'elapsed_time' => $status['elapsed_time']
];
}
/**
* リソースをクリーンアップ
*/
public function close() {
if (isset($this->pipes[1])) {
fclose($this->pipes[1]);
}
if (isset($this->pipes[2])) {
fclose($this->pipes[2]);
}
if (is_resource($this->process)) {
return proc_close($this->process);
}
return -1;
}
/**
* デストラクタ
*/
public function __destruct() {
$this->close();
}
}
// 使用例
try {
$monitor = new ProcessMonitor();
echo "=== プロセス監視デモ ===\n";
// 長時間実行されるコマンドを開始
$status = $monitor->start('ping -c 10 google.com');
echo "プロセス開始 (PID: {$status['pid']})\n\n";
// 実行中は状態を表示
$lastUpdate = 0;
while ($monitor->isRunning()) {
$status = $monitor->getStatus();
$elapsed = round($status['elapsed_time'], 1);
// 1秒ごとに更新
if ($elapsed - $lastUpdate >= 1) {
echo "実行中... {$elapsed}秒経過\r";
flush();
$lastUpdate = $elapsed;
}
// 出力があれば表示
$output = $monitor->readOutput();
if ($output) {
echo "\n" . $output;
}
usleep(100000);
}
echo "\n\nプロセス完了\n";
$status = $monitor->getStatus();
echo "終了コード: {$status['exitcode']}\n";
echo "実行時間: " . round($status['elapsed_time'], 2) . "秒\n";
$monitor->close();
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
2. 並列処理マネージャー
<?php
/**
* 複数のプロセスを並列実行して管理
*/
class ParallelProcessManager {
private $processes = [];
/**
* プロセスを追加
*/
public function addProcess($id, $command) {
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
$process = proc_open($command, $descriptorspec, $pipes);
if (!is_resource($process)) {
throw new Exception("プロセス '{$id}' の起動に失敗しました");
}
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$this->processes[$id] = [
'process' => $process,
'pipes' => $pipes,
'start_time' => microtime(true),
'output' => '',
'errors' => ''
];
return $this;
}
/**
* すべてのプロセスのステータスを取得
*/
public function getAllStatus() {
$statuses = [];
foreach ($this->processes as $id => $proc) {
$status = proc_get_status($proc['process']);
if ($status) {
$status['id'] = $id;
$status['elapsed_time'] = microtime(true) - $proc['start_time'];
$statuses[$id] = $status;
}
}
return $statuses;
}
/**
* 実行中のプロセス数を取得
*/
public function getRunningCount() {
$count = 0;
foreach ($this->getAllStatus() as $status) {
if ($status['running']) {
$count++;
}
}
return $count;
}
/**
* すべてのプロセスが完了するまで待つ
*/
public function waitAll($callback = null) {
$results = [];
while ($this->getRunningCount() > 0) {
foreach ($this->processes as $id => &$proc) {
$status = proc_get_status($proc['process']);
if (!$status['running'] && !isset($results[$id])) {
// プロセスが完了した
$proc['output'] .= stream_get_contents($proc['pipes'][1]);
$proc['errors'] .= stream_get_contents($proc['pipes'][2]);
fclose($proc['pipes'][1]);
fclose($proc['pipes'][2]);
$results[$id] = [
'exit_code' => $status['exitcode'],
'output' => $proc['output'],
'errors' => $proc['errors'],
'elapsed_time' => microtime(true) - $proc['start_time']
];
proc_close($proc['process']);
// コールバックを呼ぶ
if ($callback !== null) {
call_user_func($callback, $id, $results[$id]);
}
} else if ($status['running']) {
// 出力を読み取る
$proc['output'] .= stream_get_contents($proc['pipes'][1]);
$proc['errors'] .= stream_get_contents($proc['pipes'][2]);
}
}
usleep(100000); // 0.1秒待機
}
return $results;
}
/**
* 進行状況を表示しながら待つ
*/
public function waitAllWithProgress() {
$totalProcesses = count($this->processes);
$completed = 0;
echo "並列処理開始: {$totalProcesses}個のプロセス\n";
echo str_repeat("-", 50) . "\n";
$results = $this->waitAll(function($id, $result) use (&$completed, $totalProcesses) {
$completed++;
$status = $result['exit_code'] === 0 ? '✓' : '✗';
$time = round($result['elapsed_time'], 2);
printf(
"[%d/%d] %s %s (%.2f秒)\n",
$completed,
$totalProcesses,
$status,
$id,
$time
);
});
echo str_repeat("-", 50) . "\n";
echo "すべて完了\n";
return $results;
}
}
// 使用例
try {
$manager = new ParallelProcessManager();
// 複数のタスクを追加
$manager->addProcess('task1', 'sleep 3 && echo "Task 1 done"');
$manager->addProcess('task2', 'sleep 1 && echo "Task 2 done"');
$manager->addProcess('task3', 'sleep 2 && echo "Task 3 done"');
$manager->addProcess('task4', 'sleep 4 && echo "Task 4 done"');
// 進行状況を表示しながら実行
$startTime = microtime(true);
$results = $manager->waitAllWithProgress();
$totalTime = microtime(true) - $startTime;
echo "\n=== 実行結果 ===\n";
foreach ($results as $id => $result) {
echo "\n[{$id}]\n";
echo "出力: " . trim($result['output']) . "\n";
if ($result['errors']) {
echo "エラー: " . trim($result['errors']) . "\n";
}
}
echo "\n総実行時間: " . round($totalTime, 2) . "秒\n";
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
/* 出力例:
並列処理開始: 4個のプロセス
--------------------------------------------------
[1/4] ✓ task2 (1.01秒)
[2/4] ✓ task3 (2.01秒)
[3/4] ✓ task1 (3.01秒)
[4/4] ✓ task4 (4.01秒)
--------------------------------------------------
すべて完了
=== 実行結果 ===
[task1]
出力: Task 1 done … 総実行時間: 4.02秒 */ ?>
3. リアルタイム出力付きコマンド実行
<?php
/**
* リアルタイム出力を表示しながらコマンドを実行
*/
class RealtimeCommandExecutor {
/**
* コマンドを実行してリアルタイムで出力を表示
*/
public static function execute($command, $options = []) {
$defaults = [
'timeout' => null,
'show_output' => true,
'show_errors' => true,
'buffer_size' => 4096
];
$options = array_merge($defaults, $options);
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["pipe", "w"],
2 => ["pipe", "w"]
];
$process = proc_open($command, $descriptorspec, $pipes);
if (!is_resource($process)) {
throw new Exception('プロセスの起動に失敗しました');
}
fclose($pipes[0]);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$startTime = microtime(true);
$output = '';
$errors = '';
while (true) {
$status = proc_get_status($process);
// タイムアウトチェック
if ($options['timeout'] !== null) {
$elapsed = microtime(true) - $startTime;
if ($elapsed > $options['timeout']) {
proc_terminate($process);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
throw new Exception("コマンドがタイムアウトしました");
}
}
// 標準出力を読み取る
$out = fread($pipes[1], $options['buffer_size']);
if ($out !== false && $out !== '') {
$output .= $out;
if ($options['show_output']) {
echo $out;
flush();
}
}
// 標準エラー出力を読み取る
$err = fread($pipes[2], $options['buffer_size']);
if ($err !== false && $err !== '') {
$errors .= $err;
if ($options['show_errors']) {
fwrite(STDERR, $err);
}
}
// プロセスが終了していればループを抜ける
if (!$status['running']) {
break;
}
usleep(10000); // 0.01秒待機
}
// 残りの出力を読み取る
$output .= stream_get_contents($pipes[1]);
$errors .= stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);
return [
'exit_code' => $exitCode,
'success' => ($exitCode === 0),
'output' => $output,
'errors' => $errors,
'elapsed_time' => microtime(true) - $startTime
];
}
}
// 使用例
try {
echo "=== パッケージ更新実行中 ===\n";
$result = RealtimeCommandExecutor::execute(
'apt-get update 2>&1',
[
'timeout' => 60,
'show_output' => true
]
);
echo "\n=== 実行完了 ===\n";
echo "終了コード: {$result['exit_code']}\n";
echo "実行時間: " . round($result['elapsed_time'], 2) . "秒\n";
if (!$result['success']) {
echo "エラーが発生しました\n";
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
4. バックグラウンドジョブマネージャー
<?php
/**
* バックグラウンドジョブ管理クラス
*/
class BackgroundJobManager {
private $jobs = [];
private $jobFile;
public function __construct($jobFile = 'jobs.json') {
$this->jobFile = $jobFile;
$this->loadJobs();
}
/**
* ジョブを開始
*/
public function startJob($name, $command) {
$descriptorspec = [
0 => ["pipe", "r"],
1 => ["file", "/tmp/{$name}_stdout.log", "a"],
2 => ["file", "/tmp/{$name}_stderr.log", "a"]
];
$process = proc_open($command, $descriptorspec, $pipes);
if (!is_resource($process)) {
throw new Exception("ジョブ '{$name}' の起動に失敗しました");
}
fclose($pipes[0]);
$status = proc_get_status($process);
$this->jobs[$name] = [
'pid' => $status['pid'],
'command' => $command,
'start_time' => time(),
'status' => 'running'
];
$this->saveJobs();
// プロセスはバックグラウンドで実行し続ける
// このスクリプトは終了しても良い
return $status['pid'];
}
/**
* ジョブの状態を確認
*/
public function checkJob($name) {
if (!isset($this->jobs[$name])) {
return null;
}
$job = $this->jobs[$name];
// プロセスが実行中かチェック(UNIXのみ)
if (PHP_OS_FAMILY !== 'Windows') {
$running = file_exists("/proc/{$job['pid']}");
if (!$running && $job['status'] === 'running') {
$this->jobs[$name]['status'] = 'completed';
$this->jobs[$name]['end_time'] = time();
$this->saveJobs();
}
}
return $this->jobs[$name];
}
/**
* すべてのジョブの状態を取得
*/
public function getAllJobs() {
$statuses = [];
foreach ($this->jobs as $name => $job) {
$statuses[$name] = $this->checkJob($name);
}
return $statuses;
}
/**
* ジョブを停止
*/
public function stopJob($name) {
$job = $this->checkJob($name);
if (!$job || $job['status'] !== 'running') {
throw new Exception("ジョブ '{$name}' は実行中ではありません");
}
// プロセスにSIGTERMシグナルを送信
if (function_exists('posix_kill')) {
posix_kill($job['pid'], SIGTERM);
$this->jobs[$name]['status'] = 'stopped';
$this->jobs[$name]['end_time'] = time();
$this->saveJobs();
return true;
}
throw new Exception('この環境ではジョブの停止がサポートされていません');
}
/**
* ジョブのログを取得
*/
public function getJobLogs($name, $type = 'stdout') {
$logFile = "/tmp/{$name}_{$type}.log";
if (file_exists($logFile)) {
return file_get_contents($logFile);
}
return '';
}
/**
* ジョブ情報を保存
*/
private function saveJobs() {
file_put_contents($this->jobFile, json_encode($this->jobs, JSON_PRETTY_PRINT));
}
/**
* ジョブ情報を読み込み
*/
private function loadJobs() {
if (file_exists($this->jobFile)) {
$this->jobs = json_decode(file_get_contents($this->jobFile), true) ?: [];
}
}
}
// 使用例
try {
$manager = new BackgroundJobManager();
// バックグラウンドジョブを開始
echo "ジョブを開始しています...\n";
$pid = $manager->startJob('backup', 'tar -czf /backup/data.tar.gz /data');
echo "バックアップジョブを開始しました (PID: {$pid})\n\n";
// ジョブの状態を確認
sleep(2);
$jobs = $manager->getAllJobs();
echo "=== 実行中のジョブ ===\n";
foreach ($jobs as $name => $job) {
$elapsed = time() - $job['start_time'];
echo "ジョブ名: {$name}\n";
echo " PID: {$job['pid']}\n";
echo " ステータス: {$job['status']}\n";
echo " 実行時間: {$elapsed}秒\n";
echo " コマンド: {$job['command']}\n\n";
}
} catch (Exception $e) {
echo "エラー: " . $e->getMessage() . "\n";
}
?>
proc_get_statusの重要な注意点
注意点1: 複数回呼び出すと終了コードが失われる
<?php
$process = proc_open('exit 42', $descriptorspec, $pipes);
// 最初の呼び出し
$status1 = proc_get_status($process);
echo "1回目: running={$status1['running']}, exitcode={$status1['exitcode']}\n";
// 2回目以降の呼び出しではexitcodeが-1になる
$status2 = proc_get_status($process);
echo "2回目: running={$status2['running']}, exitcode={$status2['exitcode']}\n";
// ✅ 解決策: ステータスを変数に保存して再利用
?>
注意点2: プロセスが終了していてもrunning=trueの場合がある
<?php
// プロセスが終了した直後は、まだrunning=trueの場合がある
// 少し待ってから再度チェックする必要がある
while (true) {
$status = proc_get_status($process);
if (!$status['running']) {
break;
}
usleep(100000); // 0.1秒待機
}
?>
まとめ
proc_get_status関数は、PHPで外部プロセスの状態を監視するための重要な関数です。以下のポイントを押さえておきましょう。
- プロセスの実行状態をリアルタイムで確認可能
runningフラグで実行中かどうかを判定exitcodeで終了コードを取得(プロセス終了後)pidでプロセスIDを取得- タイムアウト処理の実装に必須
- 並列処理の管理に活用
- 複数回呼び出すと終了コードが失われるので注意
- 長時間実行されるプロセスの進行状況監視に最適
proc_get_statusを活用することで、外部プロセスを適切に監視・管理でき、タイムアウト処理や並列実行など、高度なプロセス制御が実現できます!
