コマンドを実行し、入出力用にファイルポインタを開く

# proc_open

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

proc_open — コマンドを実行し、入出力用にファイルポインタを開く

### 説明

function proc_open(
array|string `$command`,
array `$descriptor_spec`,
array `&$pipes`,
?string `$cwd` = `null`,
?array `$env_vars` = `null`,
?array `$options` = `null`
): resource|false

proc_open() は popen() と よく似ていますが、プログラムの実行をさらに細かく制御できる点で違います。

### パラメータ

`command`

  実行するコマンドラインを string として渡します。 特殊な文字は適切にエスケープし、適切にクォートしなければなりません。

  > 注意: Windows では、 `options` の `bypass_shell` を `true` に設定しないと、 cmd.exe (実際は`%ComSpec%`) に `command` の値を クォートしないまま (つまり、proc_open() に渡されたそのままの値を) `/c` と一緒に渡してしまいます。 この振る舞いによって、cmd.exe が `command` からクォートを削除してしまうため、 (詳細は cmd.exe のドキュメントを参照ください) 予期しない、潜在的に危険とさえ言える結果になります。なぜなら、 cmd.exe のエラーメッセージには、 渡された `command` (の一部) が含まれる可能性があるからです(下の例を見てください)。

  PHP 7.4.0 以降、`command` にはコマンドの引数も含めた array を渡せるようになりました。 この場合、プロセスは直接(シェルを介さずに)オープンされ、PHP が必要な引数のエスケープを全て行います。

  > 注意:

  > Windows では、array で渡されるコマンドライン引数のエスケープは、 コマンドライン引数の解釈が VCランタイムによって行われるものと互換性があることを前提にして行われます。

`descriptor_spec`

  数値添字の配列で、ディスクリプタ番号をキーとし、PHP がその ディスクリプタをどのように子プロセスに渡すかを表すのが 対応する値となります。 0 が標準入力 (stdin)、1 が標準出力 (stdout) で、 2 が標準エラー出力 (stderr) となります。

  各要素は、次のようになります。

  - プロセスに渡すパイプをあらわす配列。 最初の要素はディスクリプタの型で、2 番目以降の要素がその型に対応するオプションとなります。 使用できる型は `pipe` (2 番目の要素は、 プロセスにパイプの読み込み側を渡すのなら `r`、 書き込み側を渡すのなら `w`) および `file` (2 番目の要素はファイル名、 3 番目の要素は fopen() と同じファイルモード) です。 `w` 以外に何を指定しても、 `r` のように扱われるので注意して下さい。
  - 実際のファイルディスクリプタ (オープンしたファイルやソケット、 `STDIN` など) をあらわすストリームリソース。

  ファイルディスクリプタの番号は、特に 0, 1, 2 に限られているわけでは ありません。有効であるどのようなファイルディスクリプタの番号も指定でき、 それは子プロセスに渡されます。これにより、あるスクリプトと、 子プロセスとして起動している別のスクリプトとの間で通信ができます。 特に、これは PGP や GPG、openssl といったプログラムにパスフレーズを より安全な方法で渡したいとき威力を発揮します。 補助的なファイルディスクリプタを介して、そのようなプログラムの 状態を取得するのにも便利です。

`pipes`

  PHP 側で生成されたパイプの終端にあたる ファイルポインタの配列。

`cwd`

  コマンドの初期作業ディレクトリ。 完全パスである必要があります。 デフォルト値 (現在の PHP プロセスの作業ディレクトリ) を使用したい場合は `null` を指定します。

`env_vars`

  実行するコマンドのための環境変数の配列。 現在の PHP プロセスと同じ環境変数を使用する場合は `null` を指定します。

`options`

  その他の追加オプションを指定することが可能です。 現在サポートされているオプションは次の通りです。

  - `suppress_errors` (windows のみ): `true` にすると、この関数が出力するエラーを抑制します。
  - `bypass_shell` (windows のみ): `true` にすると、`cmd.exe` シェルをバイパスします。
  - `blocking_pipes` (windows のみ): `true` に設定すると、パイプを強制的にブロッキングモードにします。
  - `create_process_group` (Windows のみ): `true` に設定すると、 子プロセスが `CTRL` イベントを ハンドルすることを許可します。
  - `create_new_console` (windows のみ): 新しいプロセスは親の console を継承せず、新しい console を持ちます。

### 戻り値

プロセスを表すリソースを返します。このリソースは、使用し終えた際に proc_close() を使用して解放する必要があります。 失敗した場合は `false` を返します。

### エラー / 例外

PHP 8.3.0 以降では、 `command` に配列を指定したのに、空でない要素を最低一つ含んでいない場合 ValueError をスローするようになりました。

### 変更履歴

バージョン | 説明
--- | ---
8.3.0 | `command` に配列を指定したのに、空でない要素を最低一つ含んでいない場合 ValueError をスローするようになりました。
7.4.4 | `options` パラメータに オプション `create_new_console` が追加されました。
7.4.0 | proc_open() 関数は、 `command` に array を渡せるようになりました。
7.4.0 | `options` パラメータに オプション `create_process_group` が追加されました。

### 例

例1 proc_open() の例

    <?php
    $descriptorspec = array(
       0 => array("pipe", "r"),  // stdin は、子プロセスが読み込むパイプです。
       1 => array("pipe", "w"),  // stdout は、子プロセスが書き込むパイプです。
       2 => array("file", "/tmp/error-output.txt", "a") // stderr はファイルで、そこに書き込みます。
    );

    $cwd = '/tmp';
    $env = array('some_option' => 'aeiou');

    $process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);

    if (is_resource($process)) {
        // $pipes はこの時点で次のような形を取っています。
        // 0 => 子プロセスの stdin に繋がれた書き込み可能なハンドル
        // 1 => 子プロセスの stdout に繋がれた読み込み可能なハンドル
        // すべてのエラー出力は /tmp/error-output.txt に書き込みされます。

        fwrite($pipes[0], '<?php print_r($_ENV); ?>');
        fclose($pipes[0]);

        echo stream_get_contents($pipes[1]);
        fclose($pipes[1]);

        // デッドロックを避けるため、proc_close を呼ぶ前に
        // すべてのパイプを閉じることが重要です。
        $return_value = proc_close($process);

        echo "command returned $return_value\n";
    }
    ?>

上の例の出力は、 たとえば以下のようになります。

    Array
    (
        [some_option] => aeiou
        [PWD] => /tmp
        [SHLVL] => 1
        [_] => /usr/local/bin/php
    )
    command returned 0

例2 proc_open() 関数の癖(Windows限定)

次のプログラムで、ファイル filename.txt にある `search` というテキストを検索し、結果を出力したいのですが、 実際にはかなり異なる振る舞いをします。

    <?php
    $descriptorspec = [STDIN, STDOUT, STDOUT];
    $cmd = '"findstr" "search" "filename.txt"';
    $proc = proc_open($cmd, $descriptorspec, $pipes);
    proc_close($proc);
    ?>

上の例の出力は以下となります。

    'findstr" "search" "filename.txt' is not recognized as an internal or external command,
    operable program or batch file.

この振る舞いを避けるには、 `command` を追加のクォートで囲めば通常は十分です:

    $cmd = '""findstr" "search" "filename.txt""';

### 注意

> 注意:

> Windows における互換性: 2 (stderr) よりも大きな番号のディスクリプタは 子プロセスに継承可能なハンドルとして渡されますが、 Windows のアーキテクチャは、ファイルディスクリプタの番号と より低レベルなハンドルを関連付けないので、子プロセスは、 それらのハンドルにアクセスする術を持ちません。stdin, stdout, stderr は期待通り動きます。

> 注意:

> もし単方向(一方向)のパイプを利用したいだけでしたら、 popen() を使うほうがより簡単です。

### 参考

- popen() - プロセスへのファイルポインタをオープンする
- exec() - 外部プログラムを実行する
- system() - 外部プログラムを実行し、出力を表示する
- passthru() - 外部プログラムを実行し、未整形の出力を表示する
- stream_select() - select() システムコールと同等の操作を、 ストリームの配列に対して seconds と microseconds で指定されたタイムアウト時間をもって行う
- バッククォート演算子

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open