proc_open

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

説明

resourcefalse proc_open(
    arraystring $command,
    array $descriptor_spec,
    array &$pipes,
    stringnull $cwd = null,
    arraynull $env_vars = null,
    arraynull $options = null
)

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

パラメータ

command

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

注意: Windows では、 optionsbypass_shelltrue に設定しないと、 cmd.exe (実際は%ComSpec%) に command の値を クォートしないまま (つまり、proc_open に渡されたそのままの値を) /c と一緒に渡してしまいます。 この振る舞いによって、cmd.execommand からクォートを削除してしまうため、 (詳細は 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 番目の要素はファイル名) です。 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 を返します。

変更履歴

バージョン 説明
7.4.4 options パラメータに オプション create_new_console が追加されました。
7.4.0 proc_open 関数は、 commandarray を渡せるようになりました。
7.4.0 options パラメータに オプション create_process_group が追加されました。

例1 A proc_open の例

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

$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 を使うほうがより簡単です。

参考