Запуск внешних процессов в PHP: детальное описание методов
Основное решение: функция exec()
Функция exec() позволяет выполнить внешнюю команду и получить последнюю строку её вывода. Это наиболее часто используемый метод для простого запуска системных команд и захвата результата.
Как выполнить команду и сохранить вывод в массив?
<?php
$output = [];
$return_var = 0;
exec('ls -la', $output, $return_var);
print_r($output);
?>
Пояснение: первый параметр - команда, второй массив для строк вывода, третий - код возврата. Код 0 означает успех.
Типичные проблемы и их решения
- Если вывод содержит много строк, exec() возвращает только последнюю. Используйте второй аргумент для получения всего вывода.
- Команда может не выполняться из-за отключения функции в php.ini (disable_functions). Проверьте настройки сервера.
- Проблемы с правами: пользователь www-data может не иметь доступа к некоторым командам. Настройте sudoers для определённых команд.
Как получить весь вывод команды в виде строки без разбиения на массив?
Используйте функцию shell_exec(). Она возвращает весь вывод команды в виде одной строки.
<?php
$result = shell_exec('whoami');
echo $result; // например, 'www-data'
?>
При ошибке выполнения shell_exec() возвращает NULL. Проверяйте результат.
Как вывести вывод команды непосредственно в браузер в реальном времени?
Функция system() выполняет команду и сразу выводит результат. Подходит для потокового вывода.
<?php
system('ping -c 4 google.com');
?>
Вывод system() не может быть захвачен в переменную напрямую. Если нужен результат, используйте буферизацию ob_start().
Как выполнить команду и получить сырой бинарный вывод (например, изображение)?
passthru() аналогична system(), но передаёт необработанные данные. Подходит для запуска программ, выводящих бинарные данные.
<?php
header('Content-Type: image/png');
passthru('cat image.png');
?>
Не забывайте устанавливать правильные заголовки HTTP перед выводом.
Как выполнить команду с возможностью чтения/записи в её потоки ввода-вывода?
Используйте proc_open() для полного контроля над процессами: stdin, stdout, stderr.
<?php
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$process = proc_open('cat', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], 'Hello from PHP');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
}
?>
Не забудьте закрыть все pipes и вызвать proc_close(). Иначе может произойти утечка процессов.
Как открыть однонаправленный канал для чтения или записи?
Функция popen() открывает канал к процессу. Режим 'r' для чтения, 'w' для записи.
<?php
$handle = popen('/usr/bin/uptime', 'r');
while ($line = fgets($handle)) {
echo $line;
}
pclose($handle);
?>
popen() не даёт доступа к stderr. Для этого используйте proc_open().
Расширенные примеры выполнения команд
<?php
// 1. Экранирование аргументов для безопасности
$filename = 'file with spaces.txt';
$escaped = escapeshellarg($filename);
$cmd = 'cat ' . $escaped;
echo system($cmd);
?>
Вывод: содержимое файла 'file with spaces.txt'
<?php
// 2. Выполнение команды с ограничением времени (timeout) через proc_open
$cmd = 'sleep 10; echo done';
$descriptorspec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
stream_set_blocking($pipes[1], 0);
$timeout = 5;
$start = time();
$output = '';
while (true) {
$status = proc_get_status($process);
if (!$status['running']) break;
if (time() - $start > $timeout) {
proc_terminate($process, 9);
$output = 'Timeout';
break;
}
usleep(100000);
$output .= stream_get_contents($pipes[1]);
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
echo $output;
}
?>
Вывод: Timeout (команда прервана через 5 секунд)
<?php
// 3. Запуск фонового процесса без ожидания (linux)
$cmd = 'nohup php long_script.php > /dev/null 2>&1 &';
exec($cmd);
echo 'Фоновый процесс запущен';
?>
Вывод: Фоновый процесс запущен
<?php
// 4. Передача переменных окружения через proc_open
$env = ['MYVAR' => 'value123'];
$descriptorspec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open('echo $MYVAR', $descriptorspec, $pipes, null, $env);
if (is_resource($process)) {
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
}
?>
Вывод: value123
<?php
// 5. Использование shell_exec с конвейером
$result = shell_exec('ps aux | grep php | grep -v grep | wc -l');
echo 'Количество процессов PHP: ' . (int)$result;
?>
Вывод: Количество процессов PHP: 4
<?php
// 6. Обработка ошибок stderr через proc_open
$cmd = 'ls /nonexistent';
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open($cmd, $descriptorspec, $pipes);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
echo 'STDOUT: ' . $stdout . PHP_EOL;
echo 'STDERR: ' . $stderr;
?>
STDOUT: STDERR: ls: cannot access '/nonexistent': No such file or directory