Avatar ·

В чем разница между shell_exec() и exec() в PHP для выполнения команд?

Всем привет! Я новичок в PHP и пытаюсь выполнять системные команды из скрипта. Нашел две функции: shell_exec() и exec(), но не понимаю, в чем между ними разница, и какую когда использовать. У меня есть простой скрипт, который должен вывести список файлов в директории: ```php ``` Это работает, и я вижу список файлов. Но когда я пробую exec(): ```php ``` Тоже вроде работает, но результат приходит в виде массива, а не строки. Мне нужно выполнять разные команды: иногда просто проверить, успешно ли выполнилась команда (например, создать папку), иногда получить вывод для дальнейшей обработки (например, список процессов), а иногда выполнить что-то в фоне (например, запустить скрипт на сервере). Подскажите, пожалуйста: 1. В чем принципиальная разница между shell_exec() и exec()? 2. Какую функцию лучше использовать для разных задач? 3. Есть ли какие-то подводные камни или проблемы безопасности? Заранее спасибо за помощь! <<>>

Основные различия между shell_exec() и exec()

Обе функции выполняют системные команды через оболочку (shell), но возвращают результаты по-разному. Главное отличие — в формате возвращаемого значения и возможности получить код возврата.

1. shell_exec() — возвращает весь вывод как строку

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

// Пример: получаем список файлов как строку
$files = shell_exec('ls -la');
echo "Файлы:\n" . $files;

// Если команда не имеет вывода (например, mkdir)
$result = shell_exec('mkdir /tmp/testdir');
var_dump($result); // null — вывода нет

Когда использовать:

  • Нужен полный вывод команды в виде текста
  • Команда гарантированно возвращает результат (например, whoami, date)
  • Вы не планируете обрабатывать вывод построчно

2. exec() — возвращает вывод как массив и код возврата

exec() передает вывод команды в массив (каждая строка — отдельный элемент) и позволяет получить код возврата (0 — успех, любое другое число — ошибка).

// Пример: получаем список файлов построчно
exec('ls -la', $output, $return_code);
echo "Код возврата: $return_code\n";
echo "Файлы:\n";
foreach ($output as $line) {
    echo "- $line\n";
}

// Проверяем успешность создания папки
exec('mkdir /tmp/testdir', $output, $return_code);
if ($return_code === 0) {
    echo "Папка создана успешно";
} else {
    echo "Ошибка создания папки";
}

Когда использовать:

  • Нужно знать, успешно ли выполнилась команда (через код возврата)
  • Вывод нужно обрабатывать построчно
  • Команда может вернуть ошибку, и вы хотите это отследить

Дополнительные функции: system() и passthru()

Для полноты картины стоит упомянуть еще две функции:

  • system() — выполняет команду и сразу выводит результат в браузер/консоль. Возвращает последнюю строку вывода.
  • passthru() — выполняет команду и передает сырой бинарный вывод (полезно для изображений, аудио).
// system() — вывод сразу на экран
system('ls -la'); // строки появятся в браузере

// passthru() — для бинарных данных
header('Content-Type: image/png');
passthru('cat image.png');

Важные замечания по безопасности

Все эти функции крайне опасны, если вы передаете пользовательский ввод. Никогда не делайте так:

// ОПАСНО! Никогда так не делайте!
$user_input = $_GET['filename'];
shell_exec("rm -rf $user_input"); // злоумышленник может выполнить любую команду

Всегда используйте escapeshellcmd() или escapeshellarg() для экранирования:

// Безопасный вариант
$filename = escapeshellarg($user_input);
shell_exec("ls -la " . $filename);

Краткая таблица выбора:

  • Нужен весь вывод как строка → shell_exec()
  • Нужен код возврата или построчная обработка → exec()
  • Нужно сразу вывести результат пользователю → system()
  • Работа с бинарными данными → passthru()
<<>>

Альтернативный подход: Использование proc_open() для максимального контроля

Если вам нужен полный контроль над выполнением команды (например, управление stdin, stdout, stderr по отдельности, или выполнение в фоне), лучше использовать proc_open().

// Запускаем команду с отдельным управлением потоками
$descriptorspec = [
    0 => ['pipe', 'r'], // stdin
    1 => ['pipe', 'w'], // stdout
    2 => ['pipe', 'w']  // stderr
];

$process = proc_open('ls -la', $descriptorspec, $pipes);

if (is_resource($process)) {
    // Читаем stdout
    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    
    // Читаем stderr
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);
    
    // Закрываем процесс
    $return_code = proc_close($process);
    
    echo "Вывод: $stdout\n";
    echo "Ошибки: $stderr\n";
    echo "Код возврата: $return_code\n";
}

Преимущества proc_open():

  • Раздельное чтение stdout и stderr
  • Возможность отправлять данные в stdin команды
  • Неблокирующий режим (можно выполнять в фоне)
  • Полный контроль над дескрипторами

Недостатки:

  • Сложнее в использовании
  • Требует больше кода
  • Избыточен для простых команд

Для большинства повседневных задач exec() или shell_exec() более чем достаточно. Но если вы пишете что-то сложное (например, интерактивный терминал или управление процессами), proc_open() — ваш выбор.