Системные вызовы в 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 сек