PHP и системные вызовы: практическое руководство по exec, system, passthru

Раздел: Администрирование PHP -> Системные команды

Обзор системных команд в PHP

PHP предоставляет несколько функций для выполнения команд операционной системы. Наиболее распространённая из них - system(). Она отправляет команду в shell и сразу выводит результат в стандартный поток вывода. Однако в зависимости от задачи могут потребоваться другие функции: exec(), passthru() или shell_exec(). В этой статье рассматриваются все варианты с примерами кода и пояснениями.

Основное решение: system()

Как выполнить команду и увидеть её вывод непосредственно в браузере или консоли?

$output = system('ls -la');
echo "Код возврата: $output";

Функция system() выполняет команду, передаёт её интерпретатору shell и сразу печатает весь вывод. Возвращает последнюю строку вывода (или false при ошибке). Вторым параметром можно получить код возврата.

Типичные ошибки:

  • Команда не найдена или нет прав на выполнение - возвращается false, код возврата будет 127 (command not found).
  • Вывод может быть очень большим и заблокировать скрипт. Для длительных команд лучше использовать exec() с буферизацией.
  • Неэкранированные аргументы приводят к уязвимостям (command injection). Рекомендуется использовать escapeshellcmd() или escapeshellarg().

Для экранирования всей команды применяют escapeshellcmd(), для отдельного аргумента - escapeshellarg():

$file = escapeshellarg($user_file);
system("cat $file");

Вариант 2: exec()

Как выполнить команду и получить её вывод в массив для дальнейшей обработки?

$output = [];
$returnCode = 0;
exec('ls -la', $output, $returnCode);
print_r($output);
echo "Код возврата: $returnCode";

exec() не выводит результат автоматически. Вторым параметром передаётся массив, куда построчно записывается вывод. Код возврата - третий параметр. Подходит для захвата вывода без прямого эха.

Проблемы:

  • При большом объёме вывода массив может переполнить память. В таких случаях используют passthru() или временный файл.
  • Символы конца строки не обрезаются, их нужно чистить вручную.

Вариант 3: passthru()

Как выполнить команду и передать бинарные данные (например, вывод изображения) напрямую в поток вывода без буферизации?

passthru('cat image.png');

passthru() работает аналогично system(), но не возвращает вывод в PHP - он напрямую отправляется в stdout. Идеально для бинарных данных (изображений, архивов), так как не изменяет содержимое.

Ошибки:

  • Если не настроен заголовок Content-Type, браузер может некорректно отобразить бинарные данные.
  • При ошибках команды может быть выведен текст ошибки в stdout, что нарушит поток данных.

Вариант 4: shell_exec()

Как получить весь вывод команды в виде одной строки?

$result = shell_exec('ls -la');
echo $result;

shell_exec() выполняет команду через shell и возвращает весь вывод в виде строки (без последнего символа новой строки). Если вывод пуст или ошибка, возвращает null.

Особенности:

  • Аналогичен обратным кавычкам в PHP: $result = `ls -la`;
  • Важно: при ошибке возвращается null, а не false. Следует проверять с помощью строгого сравнения.

Вариант 5: обратные кавычки (backticks)

Как синтаксически коротко выполнить команду?

$output = `ls -la`;
echo $output;

Это эквивалент shell_exec(). Удобно для быстрых однострочных команд, но злоупотребление снижает читаемость кода.

Предостережения:

  • Внутри кавычек нельзя использовать сложные переменные без конкатенации.
  • Не отличается по безопасности от shell_exec() - требуется экранирование.

Вариант 6: proc_open() - продвинутый контроль

Как получить полный контроль над процессом: ввод-вывод, дескрипторы, асинхронность?

$descriptorspec = array(
   0 => array("pipe", "r"),
   1 => array("pipe", "w"),
   2 => array("pipe", "w")
);
$process = proc_open('cat', $descriptorspec, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "Привет, мир!");
    fclose($pipes[0]);
    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
}

proc_open() позволяет открыть процесс с гибким управлением потоками. Используется для интерактивных команд или когда нужно подавать данные на stdin.

Сложности:

  • Требуется аккуратное закрытие всех дескрипторов и освобождение ресурса.
  • Блокировки при чтении/записи могут привести к взаимоблокировке (deadlock).

Безопасность при выполнении системных команд

Все функции выполнения команд несут риск инъекций. Никогда не передавайте непроверенные пользовательские данные напрямую в команду. Всегда используйте escapeshellarg() для аргументов и escapeshellcmd() для всей команды, если это необходимо.

$filename = $_GET['file'];
$safe = escapeshellarg($filename);
system("cat $safe");
// или с помощью exec
exec("cat $safe", $out, $code);

Также можно использовать whitelist разрешённых команд.

Расширенные примеры использования системных команд

Пример 1. Интерактивный запуск программы с передачей данных через proc_open.

Пример
$descriptors = [
   0 => ['pipe', 'r'],
   1 => ['pipe', 'w'],
   2 => ['pipe', 'w']
];
$process = proc_open('grep "PHP"', $descriptors, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "Python\nPHP\nJavaScript\n");
    fclose($pipes[0]);
    $result = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($process);
    echo "Результат grep:\n$result";
} else {
    echo "Не удалось запустить процесс.";
}
Результат grep:
PHP

Пример 2. Выполнение команды с ограничением времени выполнения (timeout).

Пример
$cmd = 'sleep 10 && echo done';
// Используем exec с временным файлом и сигналом через proc_open
$timeout = 3;
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
    $start = time();
    while (true) {
        $status = proc_get_status($process);
        if (!$status['running']) break;
        if (time() - $start > $timeout) {
            proc_terminate($process, 9);
            echo "Процесс превысил время выполнения и был завершён.";
            break;
        }
        usleep(100000);
    }
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($process);
}
Процесс превысил время выполнения и был завершён.

Пример 3. Использование shell_exec для получения полного вывода команды ping с одним запросом.

Пример
$host = 'google.com';
$cmd = sprintf('ping -c 1 %s 2>&1', escapeshellarg($host));
$output = shell_exec($cmd);
if ($output === null) {
    echo "Ошибка выполнения ping.";
} else {
    echo "
$output
"; }
PING google.com (142.250.185.78): 56 data bytes
64 bytes from 142.250.185.78: icmp_seq=0 ttl=119 time=10.2 ms
--- google.com ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 10.2/10.2/10.2/0.0 ms

Пример 4. Передача переменных окружения в подпроцесс.

Пример
$env = ['APP_ENV' => 'production', 'SECRET' => 'abc123'];
$cmd = 'printenv | grep APP_';
$descriptors = [1 => ['pipe', 'w']];
$process = proc_open($cmd, $descriptors, $pipes, null, $env);
if (is_resource($process)) {
    echo stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
}
APP_ENV=production

Пример 5. Буферизация вывода длительной команды с помощью popen и построчного чтения.

Пример
$handle = popen('tail -f /var/log/syslog', 'r');
if ($handle) {
    while (!feof($handle)) {
        $line = fgets($handle);
        if ($line !== false) {
            echo htmlspecialchars($line) . "
\n"; ob_flush(); flush(); } } pclose($handle); }

Этот пример предназначен для работы в консольном скрипте с отключенным буферизированием вывода PHP (ob_implicit_flush). В веб-сервере такая техника требует специальной настройки (выключение буферизации).

Пример 6. Экранирование аргументов для избежания инъекции.

Пример
$user_input = "'; rm -rf /; echo 'hacked";
$safe_arg = escapeshellarg($user_input);
$command = "echo $safe_arg";
echo "Команда: $command\n";
system($command);
Команда: echo ''"'"'; rm -rf /; echo ''"'"'hacked'
'\'\'; rm -rf /; echo \'hacked'

Экранирование превращает специальные символы в безопасные строки. В результате команда просто выводит введённую строку, без реального выполнения rm.

- Php system (системные команды в php (функция system()))
- Php exe (выполнение команд в php)

Системные команды в PHP (функция system()) - comments

En
Php system (php)