Команды операционной системы в PHP: выполнение и управление

Раздел: Разработка на PHP -> Выполнение PHP и команд

Основы выполнения команд ОС в PHP

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

Наиболее эффективное решение: функция exec с контролем кода возврата

Функция exec выполняет команду и возвращает последнюю строку вывода. Код завершения команды записывается в третий параметр. Это позволяет проверить успешность выполнения и избежать типичных проблем.


$output = [];
$returnCode = 0;
$lastLine = exec('ls -l', $output, $returnCode);
if ($returnCode === 0) {
    echo 'Команда выполнена успешно';
    print_r($output);
} else {
    echo 'Ошибка выполнения, код: ' . $returnCode;
}

Php выполнение команд (выполнение команд ос из php)

Пояснение:

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

Типичные проблемы:

  • Пустой массив $output при ошибке - проверяйте $returnCode.
  • Игнорирование экранирования - используйте escapeshellarg() для аргументов.
  • Длительное выполнение без таймаута - применяйте set_time_limit(0) или proc_open с управлением.

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

Для этого используется shell_exec (или обратные кавычки). Возвращает всю строку вывода, но не даёт кода возврата.


$output = shell_exec('whoami');
echo 'Текущий пользователь: ' . $output;

Php выполнение кода (выполнение php-кода)

Проблемы:

  • При ошибке команды возвращается null - сложно отличить от пустого вывода.
  • Небезопасно при подстановке пользовательских данных - требуется экранирование.

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

Функции system и passthru выводят данные напрямую в stdout. passthru подходит для бинарных данных.


echo 'Содержимое папки:';
system('ls -la');

// Для бинарных данных, например, изображения
header('Content-Type: image/png');
passthru('cat image.png');

Ошибки:

  • Нельзя перехватить вывод в переменную без буферизации.
  • system возвращает код возврата, но только последнюю строку, что неудобно.
Как выполнить команду с полным контролем над вводом/выводом и дескрипторами?

proc_open предоставляет максимальную гибкость: можно передать данные в stdin, читать из stdout и stderr, управлять процессом.


$descriptorspec = [
    0 => ['pipe', 'r'], // stdin
    1 => ['pipe', 'w'], // stdout
    2 => ['pipe', 'w'], // stderr
];
$process = proc_open('grep error', $descriptorspec, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], 'error found\nno error here\nerror again\n');
    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);
    $returnCode = proc_close($process);
    echo "Код: $returnCode\nStdout:\n$stdout\nStderr:\n$stderr";
}

Типичные сложности:

  • Блокировки при чтении/записи - используйте неблокирующий ввод/вывод или потоковую обработку.
  • Утечка ресурсов - обязательно закрывайте все pipes и вызывайте proc_close.
  • Синтаксис дескрипторов может различаться в Windows.
Как выполнить фоновую команду без ожидания результата?

В Unix можно перенаправить вывод в /dev/null и добавить & в команду. В PHP часто используют exec с перенаправлением или popen с режимом r.


exec('nohup php long_script.php > /dev/null 2>&1 &');

Проблемы:

  • Процесс может быть убит при завершении родительского скрипта (используйте nohup).
  • Нельзя легко отследить завершение, если не сохранить PID.

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

Пример 1: Запуск скрипта с передачей аргументов и безопасным экранированием

Пример

$filename = 'файл с пробелами.txt';
$escaped = escapeshellarg($filename);
$cmd = "wc -l $escaped";
$lines = shell_exec($cmd);
echo "Количество строк: " . trim($lines);
Количество строк: 42

Пояснение:

Функция escapeshellarg оборачивает строку в кавычки и экранирует спецсимволы, предотвращая инъекции.

Пример 2: Чтение stderr вместе с stdout через proc_open

Пример

$descriptorspec = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w'],
];
$process = proc_open('ls nonexistent', $descriptorspec, $pipes);
if (is_resource($process)) {
    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $ret = proc_close($process);
    echo "STDOUT: $stdout\nSTDERR: $stderr\nКод возврата: $ret";
}
STDOUT:
STDERR: ls: nonexistent: No such file or directory
Код возврата: 1

Пример 3: Выполнение команды с таймаутом через proc_open и proc_terminate

Пример

$cmd = 'sleep 10';
$process = proc_open($cmd, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
$start = time();
$timeout = 3; // секунды
$status = proc_get_status($process);
while ($status['running'] && (time() - $start) < $timeout) {
    usleep(100000); // 0.1 сек
    $status = proc_get_status($process);
}
if ($status['running']) {
    proc_terminate($process, 9); // SIGKILL
    echo "Процесс прерван по таймауту\n";
}
proc_close($process);
Процесс прерван по таймауту

Пример 4: Использование popen для потоковой обработки вывода (построчное чтение)

Пример

$handle = popen('tail -f /var/log/syslog 2>&1', 'r');
if ($handle) {
    while (!feof($handle)) {
        $line = fgets($handle);
        if ($line !== false) {
            echo $line;
            ob_flush(); flush();
        }
    }
    pclose($handle);
}

Пример 5: Множественные команды в конвейере через proc_open (объединение pipes)

Пример

$cmd1 = 'cat /etc/passwd';
$cmd2 = 'grep -v "^#"';
$cmd3 = 'cut -d: -f1';

$p1 = proc_open($cmd1, [1 => ['pipe', 'w']], $pipes1);
if (is_resource($p1)) {
    $p2 = proc_open($cmd2, [0 => $pipes1[1], 1 => ['pipe', 'w']], $pipes2);
    if (is_resource($p2)) {
        $p3 = proc_open($cmd3, [0 => $pipes2[1], 1 => ['pipe', 'w']], $pipes3);
        if (is_resource($p3)) {
            fclose($pipes1[1]);
            fclose($pipes2[1]);
            $result = stream_get_contents($pipes3[1]);
            fclose($pipes3[1]);
            proc_close($p3);
            proc_close($p2);
            proc_close($p1);
            echo $result;
        }
    }
}
root
daemon
bin
...

Пример 6: Запуск фонового процесса с сохранением PID

Пример

// Сохраняем PID в файл для последующего контроля
$cmd = 'nohup php worker.php > worker.log 2>&1 & echo $!';
$pid = trim(shell_exec($cmd));
file_put_contents('worker.pid', $pid);
$running = (bool)shell_exec("kill -0 $pid 2>/dev/null && echo 1 || echo 0");
echo ($running ? 'Работает' : 'Не найден');

Пример 7: Взаимодействие с интерактивной командой (ввод/вывод через proc_open)

Пример

$descriptorspec = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w']
];
$process = proc_open('python3 -i', $descriptorspec, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "print('Hello from PHP')\n");
    fwrite($pipes[0], "exit()\n");
    fclose($pipes[0]);
    $output = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
    echo $output;
}
Hello from PHP

Выполнение команд ОС из PHP - comments

En
Php выполнение команд (php)