Proc open: примеры (PHP)

Функция proc_open: запуск внешних процессов в PHP
Раздел: Процессы
proc_open(string $command, array $descriptor_spec, array &$pipes, ?string $cwd = null, ?array $env_vars = null, ?array $options = null): resource|false
Функция proc_open в PHP

Функция proc_open позволяет запускать внешние процессы и организовывать двустороннюю связь с ними через каналы ввода-вывода. Она используется, когда требуется сложное взаимодействие с запущенной программой: передача данных в стандартный ввод, чтение из стандартного вывода и ошибок, а также управление процессом.

Аргументы функции

command - строка команды для выполнения.

descriptorspec - массив дескрипторов, определяющий каналы связи с процессом. Ключи - номера дескрипторов (0 - stdin, 1 - stdout, 2 - stderr). Значения могут быть: массив с типом канала ('pipe', 'file', 'socket') или ресурсом.

pipes - переменная, в которую будут записаны ресурсы открытых каналов PHP.

cwd - начальная рабочая директория процесса (null - текущая директория PHP).

env - массив переменных окружения для процесса (null - наследуются от PHP).

other_options - дополнительные опции, такие как 'suppress_errors', 'bypass_shell', 'context'.

Основные примеры использования
Простой запуск команды
$descriptors = [
    0 => ["pipe", "r"],
    1 => ["pipe", "w"],
    2 => ["pipe", "w"]
];
$process = proc_open('ls -la', $descriptors, $pipes);
if (is_resource($process)) {
    fclose($pipes[0]);
    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
}
total 12
drwxr-xr-x  2 user user 4096 Mar 10 10:00 .
drwxr-xr-x 10 user user 4096 Mar  9 09:00 ..
-rw-r--r--  1 user user   56 Mar 10 10:00 test.php
Передача данных в процесс
$process = proc_open('wc -w', $descriptors, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "Hello world from PHP");
    fclose($pipes[0]);
    echo trim(stream_get_contents($pipes[1]));
    fclose($pipes[1]);
    proc_close($process);
}
4
Использование флага bypass_shell
$options = ['bypass_shell' => true];
$process = proc_open(['echo', 'test'], $descriptors, $pipes, null, null, $options);
Похожие функции в PHP
exec()

Возвращает последнюю строку вывода команды. Подходит для простых команд без сложного взаимодействия.

shell_exec()

Возвращает весь вывод команды в виде строки. Не поддерживает разделение stdout и stderr.

passthru()

Выполняет команду и передает сырой вывод напрямую в браузер. Полезно для бинарных данных.

Открывает односторонний канал к процессу. Используется, когда требуется только чтение или только запись.

Выбор функции

proc_open предпочтительна для сложных интерактивных сценариев. Для простого выполнения команд достаточно exec или shell_exec. popen подходит для однонаправленного потока данных.

Аналоги в других языках
Python: subprocess.Popen
import subprocess
proc = subprocess.Popen(['ls', '-la'], stdout=subprocess.PIPE)
output, error = proc.communicate()
print(output.decode())
Node.js: child_process.spawn
const { spawn } = require('child_process');
const ls = spawn('ls', ['-la']);
ls.stdout.on('data', (data) => {
    console.log(data.toString());
});
Bash: перенаправление потоков
ls -la 2>&1 | while read line; do
    echo "$line"
done
Отличия

Python subprocess предоставляет более высокоуровневый API. Node.js использует событийную модель. PHP proc_open работает с ресурсами и требует ручного управления каналами.

Типичные ошибки
Не закрытие каналов
$process = proc_open('sleep 10', $descriptors, $pipes);
echo "Процесс запущен";
// Каналы не закрыты, процесс может остаться зависшим
Блокировка при чтении
$process = proc_open('cat', $descriptors, $pipes);
fwrite($pipes[0], "data");
// Не закрыт stdin, cat ожидает EOF
$output = stream_get_contents($pipes[1]); // Блокировка
Игнорирование stderr
// Дескриптор stderr не указан
$descriptors = [
    0 => ["pipe", "r"],
    1 => ["pipe", "w"]
];
$process = proc_open('command_not_exist', $descriptors, $pipes);
// Ошибки потеряются
Неправильная обработка кода возврата
proc_close($process);
$status = proc_get_status($process); // Возвращает устаревшие данные
echo $status['exitcode']; // Может быть неверным
Изменения в новых версиях PHP
PHP 8.0

Добавлена возможность передавать массив команд вместо строки для безопасного выполнения без оболочки. Улучшена обработка ошибок.

PHP 8.1

Введены предупреждения при использовании небезопасных конфигураций. Улучшена производительность.

PHP 8.2

Оптимизировано управление ресурсами процессов. Добавлены новые константы для дескрипторов.

PHP 8.3

Улучшена совместимость с Windows. Добавлена поддержка дополнительных опций контекста.

Расширенные примеры
Интерактивная сессия с процессом
Пример php
$process = proc_open('bc', $descriptors, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "2 + 2\n");
    fflush($pipes[0]);
    echo "Результат: " . fgets($pipes[1]);
    
    fwrite($pipes[0], "5 * 3\n");
    fflush($pipes[0]);
    echo "Результат: " . fgets($pipes[1]);
    
    fwrite($pipes[0], "quit\n");
    fclose($pipes[0]);
    fclose($pipes[1]);
    proc_close($process);
}
Результат: 4
Результат: 15
Параллельное чтение stdout и stderr
Пример php
$process = proc_open('command_with_output', $descriptors, $pipes);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
$stdout = '';
$stderr = '';
while (true) {
    $read = [$pipes[1], $pipes[2]];
    $write = $except = null;
    if (stream_select($read, $write, $except, 1) > 0) {
        foreach ($read as $stream) {
            if ($stream === $pipes[1]) {
                $stdout .= fread($stream, 8192);
            } else {
                $stderr .= fread($stream, 8192);
            }
        }
    }
    $status = proc_get_status($process);
    if (!$status['running']) break;
}
Запись в файл через процесс
Пример php
$descriptors = [
    1 => ["file", "/tmp/output.log", "w"],
    2 => ["pipe", "w"]
];
$process = proc_open('ls -la /', $descriptors, $pipes);
proc_close($process);
// Результат в /tmp/output.log
Использование с контекстом потока
Пример php
$context = stream_context_create([
    'proc' => [
        'suppress_errors' => true,
        'bypass_shell' => true
    ]
]);
$process = proc_open(['ls', '-la'], $descriptors, $pipes, null, null, $context);
Таймаут выполнения
Пример php
$process = proc_open('sleep 30', $descriptors, $pipes);
$start = time();
while (true) {
    $status = proc_get_status($process);
    if (!$status['running']) {
        break;
    }
    if (time() - $start > 5) {
        proc_terminate($process, SIGKILL);
        echo "Процесс завершен по таймауту";
        break;
    }
    usleep(100000);
}

PHP proc_open function comments

En
Proc open Execute a command and open file pointers for input/output