Системные вызовы в PHP: запуск утилит и управление процессами

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

Выполнение системных команд в PHP: команда id и идентификатор процесса

Как наиболее эффективно выполнить системную команду id и получить её результат в PHP?

Основным решением является использование функции system(). Она выполняет внешнюю команду и выводит её результат непосредственно в стандартный поток вывода (браузер). При этом возвращается последняя строка вывода, а статус завершения сохраняется в переданном по ссылке аргументе.


<?php
$command = 'id';
system($command, $exit_code);
echo "Статус завершения: $exit_code";
?>
  

System get php (использование system с get в php)

uid=1000(www-data) gid=1000(www-data) группы=1000(www-data),4(adm)
Статус завершения: 0
  

System php id (выполнение системной команды с php id)

Этот подход подходит, когда нужно просто передать вывод команды пользователю (например, в админ-панели). Для захвата вывода в переменную следует использовать exec() или shell_exec().

Типичная ошибка: вывод не появляется

Если вывод команды слишком большой, может быть включен буферизация вывода. Решение – использовать ob_flush() и flush() либо переключить system() на passthru().


ob_implicit_flush(true);
system('id');
    

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

Функция exec() предназначена для выполнения команды и захвата каждой строки вывода в массив. Она не выводит данные автоматически.


<?php
$output = [];
exec('id', $output, $exit_code);
print_r($output);
echo "Код возврата: $exit_code";
?>
  
Array
(
    [0] => uid=1000(www-data) gid=1000(www-data) группы=1000(www-data),4(adm)
)
Код возврата: 0
  

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

Используйте shell_exec(). Она возвращает весь вывод как единую строку (или null при ошибке).


<?php
$result = shell_exec('id');
echo $result;
?>
  
uid=1000(www-data) gid=1000(www-data) группы=1000(www-data),4(adm)
  

Проблема: shell_exec возвращает null

Это происходит, если команда не найдена или запрещена настройками PHP (запрет exec() в php.ini). Проверьте директиву disable_functions.

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

proc_open() даёт полный контроль над потоками STDIN, STDOUT, STDERR. Это удобно для интерактивного взаимодействия.


<?php
$descriptors = [
    0 => ['pipe', 'r'],  // stdin
    1 => ['pipe', 'w'],  // stdout
    2 => ['pipe', 'w']   // stderr
];
$process = proc_open('id', $descriptors, $pipes);

if (is_resource($process)) {
    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $exit_code = proc_close($process);
    echo "STDOUT:\n$stdout";
    echo "STDERR:\n$stderr";
    echo "Exit code: $exit_code";
}
?>
  
STDOUT:
uid=1000(www-data) gid=1000(www-data) группы=1000(www-data),4(adm)
STDERR:
Exit code: 0
  

Как узнать идентификатор процесса самого PHP-скрипта?

Для получения PID текущего процесса PHP используется функция getmypid(). Это не системная команда, но часто требуется в сценариях администрирования.


<?php
$pid = getmypid();
echo "PID текущего процесса: $pid";
?>
  
PID текущего процесса: 12345
  

Ошибка: getmypid возвращает false

Возникает в некоторых SAPI (например, при выполнении через командную строку иногда бывает). Альтернатива – прочитать /proc/self/stat или использовать posix_getpid() (требует модуль posix).

Как объединить выполнение нескольких команд с конвейером?

Можно комбинировать команды через &&, | и т.д. прямо в строке команды. Важно экранировать пользовательский ввод.


<?php
$command = 'whoami | xargs echo "Текущий пользователь:"';
echo shell_exec($command);
?>
  
Текущий пользователь: www-data
  

Проблема безопасности: внедрение команд

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


$userInput = '; rm -rf /';
$safeCommand = 'id ' . escapeshellarg($userInput);
echo shell_exec($safeCommand);
    

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

1. Запуск фонового процесса с помощью exec и оператора &

Команда nohup вместе с & позволяет запустить долгий процесс, не блокируя PHP.

Пример

<?php
// Запускаем скрипт в фоне, перенаправляя вывод в файл
exec("nohup /usr/bin/php /path/to/long_script.php > /dev/null 2>&1 &, $output, $exit);
echo "Фоновый процесс запущен, код возврата: $exit";
?>
  
Фоновый процесс запущен, код возврата: 0
  

2. Получение PID дочернего процесса через proc_get_status

При использовании proc_open() можно получить PID запущенной команды.

Пример

<?php
$process = proc_open('sleep 30', $descriptors = [0 => ['pipe','r'], 1 => ['pipe','w']], $pipes);
$status = proc_get_status($process);
echo "PID дочернего процесса: " . $status['pid'];
proc_close($process);
?>
  
PID дочернего процесса: 12346
  

3. Выполнение команды с таймаутом (ограничение времени)

Стандартные функции не поддерживают таймауты. Решение – использовать proc_open() в паре с stream_select() или утилиту timeout из coreutils.

Пример

<?php
$command = 'timeout 5 long_running_task'; // 5 секунд
$output = shell_exec($command);
if ($output === null && stripos(shell_exec('echo $?'), '124') !== false) {
    echo "Команда превысила лимит времени";
} else {
    echo $output;
}
?>
  
Команда превысила лимит времени
  

4. Выполнение команды с передачей данных на STDIN

Используется для подачи ввода в интерактивные утилиты (например, passwd).

Пример

<?php
$descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open('grep -i error', $descriptors, $pipes);
if (is_resource($process)) {
    fwrite($pipes[0], "This is an Error\nCorrect line\nAnother error\n");
    fclose($pipes[0]);
    $output = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    proc_close($process);
    echo "Отфильтрованные строки:
$output";
}
?>
  
Отфильтрованные строки:
This is an Error
Another error
  

5. Использование popen для чтения вывода построчно

popen() открывает однонаправленный канал к команде, позволяя читать вывод по мере поступления.

Пример

<?php
$handle = popen('tail -n 3 /var/log/syslog', 'r');
while (!feof($handle)) {
    $line = fgets($handle);
    if ($line) echo $line;
}
pclose($handle);
?>
  
Jan 12 10:23:01 server sshd[1234]: Failed password for root
Jan 12 10:23:02 server sshd[1235]: Failed password for root
Jan 12 10:23:03 server sshd[1236]: Connection closed by authenticating user root
  

6. Получение имени пользователя и группы для текущего процесса PHP

Кроме команды id, можно использовать функции posix_getpwuid() и posix_getgrgid() (требуется модуль posix).

Пример

<?php
$userInfo = posix_getpwuid(posix_geteuid());
echo "Текущий пользователь: " . $userInfo['name'];
$groupInfo = posix_getgrgid(posix_getegid());
echo "\nТекущая группа: " . $groupInfo['name'];
?>
  
Текущий пользователь: www-data
Текущая группа: www-data
  

7. Сравнение производительности system, exec и shell_exec на большом объёме вывода

Для команд, генерирующих многострочный вывод (например, ps aux), exec() создаёт массив со всеми строками, что может потребовать много памяти. system() выводит потоком, не накапливая данные, поэтому он предпочтительнее для больших объёмов.

Пример

<?php
// Потоковый вывод – system
$start = microtime(true);
system('ps aux > /dev/null');
echo "system: " . (microtime(true) - $start) . " сек\n";

// Захват всего вывода – shell_exec
$start = microtime(true);
$data = shell_exec('ps aux');
echo "shell_exec: " . (microtime(true) - $start) . " сек";
?>
  
system: 0.0045 сек
shell_exec: 0.0081 сек
  

Выполнение системной команды с PHP id - comments

En
System php id (php)